mirror of
https://github.com/basicswap/basicswap.git
synced 2024-11-16 15:58:17 +00:00
particl: Can swap PARTct -> XMR
This commit is contained in:
parent
1ef71ea79b
commit
6e82961da9
19 changed files with 1807 additions and 296 deletions
|
@ -1,3 +1,3 @@
|
|||
name = "basicswap"
|
||||
|
||||
__version__ = "0.0.24"
|
||||
__version__ = "0.0.25"
|
||||
|
|
|
@ -16,10 +16,8 @@ from .rpc import (
|
|||
callrpc,
|
||||
)
|
||||
from .util import (
|
||||
pubkeyToAddress,
|
||||
)
|
||||
from .basicswap_util import (
|
||||
TemporaryError,
|
||||
pubkeyToAddress,
|
||||
)
|
||||
from .chainparams import (
|
||||
Coins,
|
||||
|
|
|
@ -26,7 +26,7 @@ import concurrent.futures
|
|||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy.orm.session import close_all_sessions
|
||||
|
||||
from .interface_part import PARTInterface, PARTInterfaceAnon
|
||||
from .interface_part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
|
||||
from .interface_btc import BTCInterface
|
||||
from .interface_ltc import LTCInterface
|
||||
from .interface_nmc import NMCInterface
|
||||
|
@ -371,6 +371,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
if coin == Coins.PART:
|
||||
self.coin_clients[Coins.PART_ANON] = self.coin_clients[coin]
|
||||
self.coin_clients[Coins.PART_BLIND] = self.coin_clients[coin]
|
||||
|
||||
if self.coin_clients[coin]['connection_type'] == 'rpc':
|
||||
if coin == Coins.XMR:
|
||||
|
@ -384,6 +385,8 @@ class BasicSwap(BaseApp):
|
|||
def ci(self, coin): # Coin interface
|
||||
if coin == Coins.PART_ANON:
|
||||
return self.coin_clients[Coins.PART]['interface_anon']
|
||||
if coin == Coins.PART_BLIND:
|
||||
return self.coin_clients[Coins.PART]['interface_blind']
|
||||
return self.coin_clients[coin]['interface']
|
||||
|
||||
def createInterface(self, coin):
|
||||
|
@ -447,6 +450,7 @@ class BasicSwap(BaseApp):
|
|||
self.coin_clients[coin]['interface'] = self.createInterface(coin)
|
||||
if coin == Coins.PART:
|
||||
self.coin_clients[coin]['interface_anon'] = PARTInterfaceAnon(self.coin_clients[coin], self.chain, self)
|
||||
self.coin_clients[coin]['interface_blind'] = PARTInterfaceBlind(self.coin_clients[coin], self.chain, self)
|
||||
elif self.coin_clients[coin]['connection_type'] == 'passthrough':
|
||||
self.coin_clients[coin]['interface'] = self.createPassthroughInterface(coin)
|
||||
|
||||
|
@ -1843,7 +1847,11 @@ class BasicSwap(BaseApp):
|
|||
msg_buf.amount = int(amount) # Amount of coin_from
|
||||
|
||||
address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK)
|
||||
msg_buf.dest_af = ci_from.decodeAddress(address_out)
|
||||
if coin_from == Coins.PART_BLIND:
|
||||
addrinfo = ci_from.rpc_callback('getaddressinfo', [address_out])
|
||||
msg_buf.dest_af = bytes.fromhex(addrinfo['pubkey'])
|
||||
else:
|
||||
msg_buf.dest_af = ci_from.decodeAddress(address_out)
|
||||
|
||||
bid_created_at = int(time.time())
|
||||
if offer.swap_type != SwapTypes.XMR_SWAP:
|
||||
|
@ -1994,6 +2002,7 @@ class BasicSwap(BaseApp):
|
|||
xmr_swap.pkbsl = ci_to.getPubkey(kbsl)
|
||||
|
||||
xmr_swap.vkbv = ci_to.sumKeys(kbvl, xmr_swap.vkbvf)
|
||||
ensure(ci_to.verifyKey(xmr_swap.vkbv), 'Invalid key, vkbv')
|
||||
xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf)
|
||||
xmr_swap.pkbs = ci_to.sumPubkeys(xmr_swap.pkbsl, xmr_swap.pkbsf)
|
||||
|
||||
|
@ -2007,32 +2016,68 @@ class BasicSwap(BaseApp):
|
|||
# MSG2F
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx(
|
||||
bid.amount,
|
||||
xmr_swap.pkal, xmr_swap.pkaf,
|
||||
xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.vkbv
|
||||
)
|
||||
xmr_swap.a_lock_tx = ci_from.fundTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate)
|
||||
xmr_swap.a_lock_tx = ci_from.fundScriptLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
xmr_swap.a_lock_tx_id = ci_from.getTxHash(xmr_swap.a_lock_tx)
|
||||
xmr_swap.a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx)
|
||||
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
|
||||
|
||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createScriptLockRefundTx(
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.pkal, xmr_swap.pkaf,
|
||||
xmr_offer.lock_time_1, xmr_offer.lock_time_2,
|
||||
xmr_offer.a_fee_rate
|
||||
xmr_offer.a_fee_rate, xmr_swap.vkbv
|
||||
)
|
||||
xmr_swap.a_lock_refund_tx_id = ci_from.getTxHash(xmr_swap.a_lock_refund_tx)
|
||||
xmr_swap.a_lock_refund_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
||||
|
||||
xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
ensure(v, 'Invalid coin A lock tx refund tx leader sig')
|
||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||
xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
ensure(v, 'Invalid coin A lock refund tx leader sig')
|
||||
|
||||
pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from))
|
||||
xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx(
|
||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||
pkh_refund_to,
|
||||
xmr_offer.a_fee_rate
|
||||
xmr_offer.a_fee_rate, xmr_swap.vkbv
|
||||
)
|
||||
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_refund_spend_tx)
|
||||
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx)
|
||||
|
||||
# Double check txns before sending
|
||||
self.log.debug('Bid: {} - Double checking chain A lock txns are valid before sending bid accept.'.format(bid_id.hex()))
|
||||
check_lock_tx_inputs = False # TODO: check_lock_tx_inputs without txindex
|
||||
_, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx(
|
||||
xmr_swap.a_lock_tx,
|
||||
xmr_swap.a_lock_tx_script,
|
||||
bid.amount,
|
||||
xmr_swap.pkal,
|
||||
xmr_swap.pkaf,
|
||||
xmr_offer.a_fee_rate,
|
||||
check_lock_tx_inputs,
|
||||
xmr_swap.vkbv)
|
||||
|
||||
_, _, lock_refund_vout = ci_from.verifyLockRefundTx(
|
||||
xmr_swap.a_lock_refund_tx,
|
||||
xmr_swap.a_lock_tx,
|
||||
xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.a_lock_tx_id,
|
||||
xmr_swap.a_lock_tx_vout,
|
||||
xmr_offer.lock_time_1,
|
||||
xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.pkal,
|
||||
xmr_swap.pkaf,
|
||||
xmr_offer.lock_time_2,
|
||||
bid.amount,
|
||||
xmr_offer.a_fee_rate,
|
||||
xmr_swap.vkbv)
|
||||
|
||||
ci_from.verifyLockRefundSpendTx(
|
||||
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx,
|
||||
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.pkal,
|
||||
lock_refund_vout, xmr_swap.a_swap_refund_value, xmr_offer.a_fee_rate,
|
||||
xmr_swap.vkbv)
|
||||
|
||||
msg_buf = XmrBidAcceptMessage()
|
||||
msg_buf.bid_msg_id = bid_id
|
||||
|
@ -2113,12 +2158,12 @@ class BasicSwap(BaseApp):
|
|||
session.remove()
|
||||
self.mxDB.release()
|
||||
|
||||
def setBidError(self, bid_id, bid, error_str, save_bid=True):
|
||||
def setBidError(self, bid_id, bid, error_str, save_bid=True, xmr_swap=None):
|
||||
self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
|
||||
bid.setState(BidStates.BID_ERROR)
|
||||
bid.state_note = 'error msg: ' + error_str
|
||||
if save_bid:
|
||||
self.saveBid(bid_id, bid)
|
||||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
||||
|
||||
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script):
|
||||
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
|
||||
|
@ -2340,7 +2385,8 @@ class BasicSwap(BaseApp):
|
|||
|
||||
ro = self.callcoinrpc(Coins.PART, 'verifyrawtransaction', [redeem_txn, [prevout]])
|
||||
ensure(ro['inputs_valid'] is True, 'inputs_valid is false')
|
||||
ensure(ro['complete'] is True, 'complete is false')
|
||||
# outputs_valid will be false if not a Particl txn
|
||||
# ensure(ro['complete'] is True, 'complete is false')
|
||||
ensure(ro['validscripts'] == 1, 'validscripts != 1')
|
||||
|
||||
if self.debug:
|
||||
|
@ -2439,7 +2485,8 @@ class BasicSwap(BaseApp):
|
|||
|
||||
ro = self.callcoinrpc(Coins.PART, 'verifyrawtransaction', [refund_txn, [prevout]])
|
||||
ensure(ro['inputs_valid'] is True, 'inputs_valid is false')
|
||||
ensure(ro['complete'] is True, 'complete is false')
|
||||
# outputs_valid will be false if not a Particl txn
|
||||
# ensure(ro['complete'] is True, 'complete is false')
|
||||
ensure(ro['validscripts'] == 1, 'validscripts != 1')
|
||||
|
||||
if self.debug:
|
||||
|
@ -2496,17 +2543,17 @@ class BasicSwap(BaseApp):
|
|||
# Bid saved in checkBidState
|
||||
|
||||
def setLastHeightChecked(self, coin_type, tx_height):
|
||||
chain_name = chainparams[coin_type]['name']
|
||||
coin_name = self.ci(coin_type).coin_name()
|
||||
if tx_height < 1:
|
||||
tx_height = self.lookupChainHeight(coin_type)
|
||||
|
||||
if len(self.coin_clients[coin_type]['watched_outputs']) == 0:
|
||||
self.coin_clients[coin_type]['last_height_checked'] = tx_height
|
||||
self.log.debug('Start checking %s chain at height %d', chain_name, tx_height)
|
||||
self.log.debug('Start checking %s chain at height %d', coin_name, tx_height)
|
||||
|
||||
if self.coin_clients[coin_type]['last_height_checked'] > tx_height:
|
||||
self.coin_clients[coin_type]['last_height_checked'] = tx_height
|
||||
self.log.debug('Rewind checking of %s chain to height %d', chain_name, tx_height)
|
||||
self.log.debug('Rewind checking of %s chain to height %d', coin_name, tx_height)
|
||||
|
||||
return tx_height
|
||||
|
||||
|
@ -2652,7 +2699,7 @@ class BasicSwap(BaseApp):
|
|||
txid = ci_from.publishTx(xmr_swap.a_lock_refund_spend_tx)
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED, '', session)
|
||||
|
||||
self.log.info('Submitted coin a lock refund spend tx for bid {}'.format(bid_id.hex()))
|
||||
self.log.info('Submitted coin a lock refund spend tx for bid {}, txid {}'.format(bid_id.hex(), txid.hex()))
|
||||
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND] = SwapTx(
|
||||
bid_id=bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND,
|
||||
|
@ -2713,7 +2760,7 @@ class BasicSwap(BaseApp):
|
|||
except Exception as ex:
|
||||
if 'Transaction already in block chain' in str(ex):
|
||||
self.log.info('Found coin a lock refund tx for bid {}'.format(bid_id.hex()))
|
||||
txid = ci_from.getTxHash(xmr_swap.a_lock_refund_tx)
|
||||
txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
||||
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||
bid_id=bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||
|
@ -2737,30 +2784,28 @@ class BasicSwap(BaseApp):
|
|||
# TODO: Timeout waiting for transactions
|
||||
bid_changed = False
|
||||
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
|
||||
utxos, chain_height = ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount)
|
||||
# Changed from ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount, xmr_swap)
|
||||
|
||||
if len(utxos) < 1:
|
||||
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount, xmr_swap)
|
||||
|
||||
if lock_tx_chain_info is None:
|
||||
return rv
|
||||
|
||||
if len(utxos) > 1:
|
||||
raise ValueError('Too many outputs for chain A lock tx')
|
||||
|
||||
utxo = utxos[0]
|
||||
if not bid.xmr_a_lock_tx.chain_height and utxo['height'] != 0:
|
||||
if not bid.xmr_a_lock_tx.chain_height and lock_tx_chain_info['height'] != 0:
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, '', session)
|
||||
|
||||
block_header = ci_from.getBlockHeaderFromHeight(utxo['height'])
|
||||
block_header = ci_from.getBlockHeaderFromHeight(lock_tx_chain_info['height'])
|
||||
|
||||
bid.xmr_a_lock_tx.block_hash = bytes.fromhex(block_header['hash'])
|
||||
bid.xmr_a_lock_tx.block_height = block_header['height']
|
||||
bid.xmr_a_lock_tx.block_time = block_header['time'] # Or median_time?
|
||||
|
||||
bid_changed = True
|
||||
if bid.xmr_a_lock_tx.chain_height != utxo['height'] and utxo['height'] != 0:
|
||||
bid.xmr_a_lock_tx.chain_height = utxo['height']
|
||||
if bid.xmr_a_lock_tx.chain_height != lock_tx_chain_info['height'] and lock_tx_chain_info['height'] != 0:
|
||||
bid.xmr_a_lock_tx.chain_height = lock_tx_chain_info['height']
|
||||
bid_changed = True
|
||||
|
||||
if utxo['depth'] >= ci_from.blocks_confirmed:
|
||||
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)
|
||||
|
@ -3233,7 +3278,7 @@ class BasicSwap(BaseApp):
|
|||
# assert(self.mxDB.locked())
|
||||
self.log.debug('checkForSpends %s', coin_type)
|
||||
|
||||
if coin_type == Coins.PART and self.coin_clients[coin_type]['have_spent_index']:
|
||||
if 'have_spent_index' in self.coin_clients[coin_type] and self.coin_clients[coin_type]['have_spent_index']:
|
||||
# TODO: batch getspentinfo
|
||||
for o in c['watched_outputs']:
|
||||
found_spend = None
|
||||
|
@ -3414,7 +3459,6 @@ class BasicSwap(BaseApp):
|
|||
ci_from = self.ci(coin_from)
|
||||
coin_to = Coins(offer_data.coin_to)
|
||||
ci_to = self.ci(coin_to)
|
||||
chain_from = chainparams[coin_from][self.chain]
|
||||
ensure(offer_data.coin_from != offer_data.coin_to, 'coin_from == coin_to')
|
||||
|
||||
self.validateSwapType(coin_from, coin_to, offer_data.swap_type)
|
||||
|
@ -3742,11 +3786,8 @@ class BasicSwap(BaseApp):
|
|||
raise ValueError('Invalid coin a pubkey.')
|
||||
xmr_swap.pkbsf = xmr_swap.pkasf
|
||||
|
||||
if not ci_to.verifyKey(xmr_swap.vkbvf):
|
||||
raise ValueError('Invalid key.')
|
||||
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkaf):
|
||||
raise ValueError('Invalid pubkey.')
|
||||
ensure(ci_to.verifyKey(xmr_swap.vkbvf), 'Invalid key, vkbvf')
|
||||
ensure(ci_from.verifyPubkey(xmr_swap.pkaf), 'Invalid pubkey, pkaf')
|
||||
|
||||
self.log.info('Received valid bid %s for xmr offer %s', bid.bid_id.hex(), bid.offer_id.hex())
|
||||
|
||||
|
@ -3800,10 +3841,7 @@ class BasicSwap(BaseApp):
|
|||
raise ValueError('Invalid coin a pubkey.')
|
||||
xmr_swap.pkbsl = xmr_swap.pkasl
|
||||
|
||||
if not ci_to.verifyKey(xmr_swap.vkbvl):
|
||||
raise ValueError('Invalid key.')
|
||||
|
||||
xmr_swap.vkbv = ci_to.sumKeys(xmr_swap.vkbvl, xmr_swap.vkbvf)
|
||||
# vkbv and vkbvl are verified in processXmrBidAccept
|
||||
xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf)
|
||||
xmr_swap.pkbs = ci_to.sumPubkeys(xmr_swap.pkbsl, xmr_swap.pkbsf)
|
||||
|
||||
|
@ -3930,6 +3968,10 @@ class BasicSwap(BaseApp):
|
|||
try:
|
||||
xmr_swap.pkal = msg_data.pkal
|
||||
xmr_swap.vkbvl = msg_data.kbvl
|
||||
ensure(ci_to.verifyKey(xmr_swap.vkbvl), 'Invalid key, vkbvl')
|
||||
xmr_swap.vkbv = ci_to.sumKeys(xmr_swap.vkbvl, xmr_swap.vkbvf)
|
||||
ensure(ci_to.verifyKey(xmr_swap.vkbv), 'Invalid key, vkbv')
|
||||
|
||||
xmr_swap.pkbvl = ci_to.getPubkey(msg_data.kbvl)
|
||||
xmr_swap.kbsl_dleag = msg_data.kbsl_dleag
|
||||
|
||||
|
@ -3938,37 +3980,36 @@ class BasicSwap(BaseApp):
|
|||
xmr_swap.a_lock_refund_tx = msg_data.a_lock_refund_tx
|
||||
xmr_swap.a_lock_refund_tx_script = msg_data.a_lock_refund_tx_script
|
||||
xmr_swap.a_lock_refund_spend_tx = msg_data.a_lock_refund_spend_tx
|
||||
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_refund_spend_tx)
|
||||
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx)
|
||||
xmr_swap.al_lock_refund_tx_sig = msg_data.al_lock_refund_tx_sig
|
||||
|
||||
# TODO: check_a_lock_tx_inputs without txindex
|
||||
# TODO: check_lock_tx_inputs without txindex
|
||||
check_a_lock_tx_inputs = False
|
||||
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx(
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
bid.amount,
|
||||
xmr_swap.pkal, xmr_swap.pkaf,
|
||||
xmr_offer.a_fee_rate,
|
||||
check_a_lock_tx_inputs
|
||||
)
|
||||
check_a_lock_tx_inputs, xmr_swap.vkbv)
|
||||
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
|
||||
|
||||
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value = ci_from.verifyLockRefundTx(
|
||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifyLockRefundTx(
|
||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.pkal, xmr_swap.pkaf,
|
||||
xmr_offer.lock_time_2,
|
||||
bid.amount, xmr_offer.a_fee_rate
|
||||
)
|
||||
bid.amount, xmr_offer.a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
ci_from.verifyLockRefundSpendTx(
|
||||
xmr_swap.a_lock_refund_spend_tx,
|
||||
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx,
|
||||
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.pkal,
|
||||
xmr_swap.a_swap_refund_value, xmr_offer.a_fee_rate
|
||||
)
|
||||
lock_refund_vout, xmr_swap.a_swap_refund_value, xmr_offer.a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
self.log.info('Checking leader\'s lock refund tx signature')
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
ensure(v, 'Invalid coin A lock refund tx leader sig')
|
||||
|
||||
bid.setState(BidStates.BID_RECEIVING_ACC)
|
||||
self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap)
|
||||
|
@ -3985,7 +4026,7 @@ class BasicSwap(BaseApp):
|
|||
except Exception as ex:
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
self.setBidError(bid.bid_id, bid, str(ex))
|
||||
self.setBidError(bid.bid_id, bid, str(ex), xmr_swap=xmr_swap)
|
||||
|
||||
def watchXmrSwap(self, bid, offer, xmr_swap):
|
||||
self.log.debug('XMR swap in progress, bid %s', bid.bid_id.hex())
|
||||
|
@ -3994,7 +4035,9 @@ class BasicSwap(BaseApp):
|
|||
coin_from = Coins(offer.coin_from)
|
||||
self.setLastHeightChecked(coin_from, xmr_swap.start_chain_a_height)
|
||||
self.addWatchedOutput(coin_from, bid.bid_id, bid.xmr_a_lock_tx.txid.hex(), bid.xmr_a_lock_tx.vout, TxTypes.XMR_SWAP_A_LOCK, SwapTypes.XMR_SWAP)
|
||||
self.addWatchedOutput(coin_from, bid.bid_id, xmr_swap.a_lock_refund_tx_id.hex(), 0, TxTypes.XMR_SWAP_A_LOCK_REFUND, SwapTypes.XMR_SWAP)
|
||||
|
||||
lock_refund_vout = self.ci(coin_from).getLockRefundTxSwapOutput(xmr_swap)
|
||||
self.addWatchedOutput(coin_from, bid.bid_id, xmr_swap.a_lock_refund_tx_id.hex(), lock_refund_vout, TxTypes.XMR_SWAP_A_LOCK_REFUND, SwapTypes.XMR_SWAP)
|
||||
bid.in_progress = 1
|
||||
|
||||
def sendXmrBidTxnSigsFtoL(self, bid_id, session):
|
||||
|
@ -4016,8 +4059,10 @@ class BasicSwap(BaseApp):
|
|||
try:
|
||||
kaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3)
|
||||
|
||||
xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES(kaf, xmr_swap.pkasl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||
xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(kaf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
prevout_amount = ci_from.getLockRefundTxSwapOutputValue(bid, xmr_swap)
|
||||
xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES(kaf, xmr_swap.pkasl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount)
|
||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||
xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(kaf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
|
||||
self.addLockRefundSigs(xmr_swap, ci_from)
|
||||
|
||||
|
@ -4038,7 +4083,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
self.log.info('Sent XMR_BID_TXN_SIGS_FL %s', xmr_swap.coin_a_lock_tx_sigs_l_msg_id.hex())
|
||||
|
||||
a_lock_tx_id = ci_from.getTxHash(xmr_swap.a_lock_tx)
|
||||
a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx)
|
||||
a_lock_tx_vout = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
|
||||
self.log.debug('Waiting for lock txn %s to %s chain for bid %s', a_lock_tx_id.hex(), ci_from.coin_name(), bid_id.hex())
|
||||
bid.xmr_a_lock_tx = SwapTx(
|
||||
|
@ -4077,12 +4122,13 @@ class BasicSwap(BaseApp):
|
|||
xmr_swap.a_lock_spend_tx = ci_from.createScriptLockSpendTx(
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.dest_af,
|
||||
xmr_offer.a_fee_rate)
|
||||
xmr_offer.a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
xmr_swap.a_lock_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_spend_tx)
|
||||
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, bid.amount) # self.a_swap_value
|
||||
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
|
||||
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)
|
||||
|
||||
# Proof leader can sign for kal
|
||||
# Prove leader can sign for kal
|
||||
xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap')
|
||||
|
||||
msg_buf = XmrBidLockSpendTxMessage(
|
||||
|
@ -4159,8 +4205,7 @@ class BasicSwap(BaseApp):
|
|||
error_msg += ', retry no. {}'.format(num_retries)
|
||||
self.log.error(error_msg)
|
||||
|
||||
str_error = str(ex)
|
||||
if num_retries < 5 and ('not enough unlocked money' in str_error or 'transaction was rejected by daemon' in str_error or self.is_transient_error(ex)):
|
||||
if num_retries < 5 and (ci_to.is_transient_error(ex) or self.is_transient_error(ex)):
|
||||
delay = random.randrange(self.min_delay_retry, self.max_delay_retry)
|
||||
self.log.info('Retrying sending xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createEventInSession(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||
|
@ -4168,7 +4213,7 @@ class BasicSwap(BaseApp):
|
|||
self.setBidError(bid_id, bid, 'publishBLockTx failed: ' + str(ex), save_bid=False)
|
||||
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
||||
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str_error, session)
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str(ex), session)
|
||||
return
|
||||
|
||||
self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
|
@ -4233,11 +4278,12 @@ class BasicSwap(BaseApp):
|
|||
kaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3)
|
||||
|
||||
al_lock_spend_sig = ci_from.decryptOtVES(kbsf, xmr_swap.al_lock_spend_tx_esig)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, al_lock_spend_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, al_lock_spend_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
ensure(v, 'Invalid coin A lock tx spend tx leader sig')
|
||||
|
||||
af_lock_spend_sig = ci_from.signTx(kaf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, af_lock_spend_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
af_lock_spend_sig = ci_from.signTx(kaf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
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 = [
|
||||
|
@ -4303,8 +4349,7 @@ class BasicSwap(BaseApp):
|
|||
error_msg += ', retry no. {}'.format(num_retries)
|
||||
self.log.error(error_msg)
|
||||
|
||||
str_error = str(ex)
|
||||
if num_retries < 100 and ('Invalid unlocked_balance' in str_error or self.is_transient_error(ex)):
|
||||
if num_retries < 100 and (ci_to.is_transient_error(ex) or self.is_transient_error(ex)):
|
||||
delay = random.randrange(self.min_delay_retry, self.max_delay_retry)
|
||||
self.log.info('Retrying sending xmr swap chain B spend tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||
|
@ -4312,7 +4357,7 @@ class BasicSwap(BaseApp):
|
|||
self.setBidError(bid_id, bid, 'spendBLockTx failed: ' + str(ex), save_bid=False)
|
||||
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
||||
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_SPEND, str_error, session)
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_SPEND, str(ex), session)
|
||||
return
|
||||
|
||||
bid.xmr_b_lock_tx.spend_txid = txid
|
||||
|
@ -4360,7 +4405,7 @@ class BasicSwap(BaseApp):
|
|||
self.log.error(error_msg)
|
||||
|
||||
str_error = str(ex)
|
||||
if num_retries < 100 and ('Invalid unlocked_balance' in str_error or self.is_transient_error(ex)):
|
||||
if num_retries < 100 and (ci_to.is_transient_error(ex) or self.is_transient_error(ex)):
|
||||
delay = random.randrange(self.min_delay_retry, self.max_delay_retry)
|
||||
self.log.info('Retrying sending xmr swap chain B refund tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createEventInSession(delay, EventTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||
|
@ -4408,7 +4453,8 @@ class BasicSwap(BaseApp):
|
|||
kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3)
|
||||
|
||||
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig)
|
||||
al_lock_refund_spend_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||
prevout_amount = ci_from.getLockRefundTxSwapOutputValue(bid, xmr_swap)
|
||||
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 = [
|
||||
|
@ -4422,7 +4468,7 @@ class BasicSwap(BaseApp):
|
|||
ensure(signed_tx, 'setTxSignature failed')
|
||||
xmr_swap.a_lock_refund_spend_tx = signed_tx
|
||||
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount)
|
||||
ensure(v, 'Invalid signature for lock refund spend txn')
|
||||
self.addLockRefundSigs(xmr_swap, ci_from)
|
||||
|
||||
|
@ -4460,13 +4506,13 @@ 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.getTxHash(xmr_swap.a_lock_spend_tx)
|
||||
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
|
||||
xmr_swap.kal_sig = msg_data.kal_sig
|
||||
|
||||
ci_from.verifyLockSpendTx(
|
||||
xmr_swap.a_lock_spend_tx,
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.dest_af, xmr_offer.a_fee_rate)
|
||||
xmr_swap.dest_af, xmr_offer.a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
ci_from.verifyCompact(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig)
|
||||
|
||||
|
@ -4532,12 +4578,12 @@ class BasicSwap(BaseApp):
|
|||
ci_from = self.ci(Coins(offer.coin_from))
|
||||
|
||||
xmr_swap.al_lock_spend_tx_esig = msg_data.al_lock_spend_tx_esig
|
||||
|
||||
try:
|
||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||
v = ci_from.verifyTxOtVES(
|
||||
xmr_swap.a_lock_spend_tx, xmr_swap.al_lock_spend_tx_esig,
|
||||
xmr_swap.pkal, xmr_swap.pkasf, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
ensure(v, 'verifyTxOtVES failed')
|
||||
xmr_swap.pkal, xmr_swap.pkasf, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
ensure(v, 'verifyTxOtVES failed for chain a lock tx leader esig')
|
||||
except Exception as ex:
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
|
@ -4648,7 +4694,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
if now - self._last_checked_watched >= self.check_watched_seconds:
|
||||
for k, c in self.coin_clients.items():
|
||||
if k == Coins.PART_ANON:
|
||||
if k == Coins.PART_ANON or k == Coins.PART_BLIND:
|
||||
continue
|
||||
if len(c['watched_outputs']) > 0:
|
||||
self.checkForSpends(k, c)
|
||||
|
@ -5203,11 +5249,11 @@ class BasicSwap(BaseApp):
|
|||
spend_tx = ci.createScriptLockRefundSpendToFTx(
|
||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||
pkh_dest,
|
||||
xmr_offer.a_fee_rate
|
||||
)
|
||||
xmr_offer.a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
vkaf = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, 3)
|
||||
sig = ci.signTx(vkaf, spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||
prevout_amount = ci.getLockRefundTxSwapOutputValue(bid, xmr_swap)
|
||||
sig = ci.signTx(vkaf, spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount)
|
||||
|
||||
witness_stack = [
|
||||
sig,
|
||||
|
|
|
@ -371,7 +371,3 @@ def isActiveBidState(state):
|
|||
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TemporaryError(ValueError):
|
||||
pass
|
||||
|
|
|
@ -11,6 +11,7 @@ from .util import (
|
|||
COIN,
|
||||
make_int,
|
||||
format_amount,
|
||||
TemporaryError,
|
||||
)
|
||||
|
||||
XMR_COIN = 10 ** 12
|
||||
|
@ -248,3 +249,21 @@ class CoinInterface:
|
|||
|
||||
def knownWalletSeed(self):
|
||||
return not self._unknown_wallet_seed
|
||||
|
||||
def chainparams(self):
|
||||
return chainparams[self.coin_type()]
|
||||
|
||||
def chainparams_network(self):
|
||||
return chainparams[self.coin_type()][self._network]
|
||||
|
||||
def is_transient_error(self, ex):
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
str_error = str(ex).lower()
|
||||
if 'not enough unlocked money' in str_error:
|
||||
return True
|
||||
if 'transaction was rejected by daemon' in str_error:
|
||||
return True
|
||||
if 'invalid unlocked_balance' in str_error:
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -340,7 +340,7 @@ class CTxIn:
|
|||
self.nSequence)
|
||||
|
||||
class CTxOutPart:
|
||||
__slots__ = ("nVersion", "nValue", "scriptPubKey")
|
||||
__slots__ = ("nVersion", "nValue", "scriptPubKey", "commitment", "data", "rangeproof")
|
||||
|
||||
def __init__(self, nValue=0, scriptPubKey=b""):
|
||||
self.nVersion = OUTPUT_TYPE_STANDARD
|
||||
|
@ -348,13 +348,37 @@ class CTxOutPart:
|
|||
self.scriptPubKey = scriptPubKey
|
||||
|
||||
def deserialize(self, f):
|
||||
self.nValue = struct.unpack("<q", f.read(8))[0]
|
||||
self.scriptPubKey = deser_string(f)
|
||||
if self.nVersion == OUTPUT_TYPE_STANDARD:
|
||||
self.nValue = struct.unpack("<q", f.read(8))[0]
|
||||
self.scriptPubKey = deser_string(f)
|
||||
elif self.nVersion == OUTPUT_TYPE_DATA:
|
||||
self.data = deser_string(f)
|
||||
elif self.nVersion == OUTPUT_TYPE_CT:
|
||||
self.commitment = f.read(33)
|
||||
self.data = deser_string(f)
|
||||
self.scriptPubKey = deser_string(f)
|
||||
self.rangeproof = deser_string(f)
|
||||
else:
|
||||
raise ValueError(f'Unknown output type {self.nVersion}')
|
||||
|
||||
def serialize(self):
|
||||
def serialize(self, with_witness=True):
|
||||
r = b""
|
||||
r += struct.pack("<q", self.nValue)
|
||||
r += ser_string(self.scriptPubKey)
|
||||
if self.nVersion == OUTPUT_TYPE_STANDARD:
|
||||
r += struct.pack("<q", self.nValue)
|
||||
r += ser_string(self.scriptPubKey)
|
||||
elif self.nVersion == OUTPUT_TYPE_DATA:
|
||||
r += ser_string(self.data)
|
||||
elif self.nVersion == OUTPUT_TYPE_CT:
|
||||
assert(len(self.commitment) == 33)
|
||||
r += self.commitment
|
||||
r += ser_string(self.data)
|
||||
r += ser_string(self.scriptPubKey)
|
||||
if with_witness:
|
||||
r += ser_string(self.rangeproof)
|
||||
else:
|
||||
r += ser_compact_size(0) # rangeproof stub
|
||||
else:
|
||||
raise ValueError(f'Unknown output type {self.nVersion}')
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -521,7 +545,7 @@ class CTransaction:
|
|||
self.sha256 = None
|
||||
self.hash = None
|
||||
|
||||
def serialize_without_witness(self):
|
||||
def serialize_without_witness(self, include_rangeproof=False):
|
||||
if self.nVersion == PARTICL_TX_VERSION:
|
||||
r = struct.pack("<H", self.nVersion)
|
||||
r += struct.pack("<I", self.nLockTime)
|
||||
|
@ -529,7 +553,7 @@ class CTransaction:
|
|||
r += ser_compact_size(len(self.vout))
|
||||
for txo in self.vout:
|
||||
r += bytes((txo.nVersion,))
|
||||
r += txo.serialize()
|
||||
r += txo.serialize(with_witness=include_rangeproof)
|
||||
return r
|
||||
r = b""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
|
@ -541,7 +565,7 @@ class CTransaction:
|
|||
# Only serialize with witness when explicitly called for
|
||||
def serialize_with_witness(self):
|
||||
if self.nVersion == PARTICL_TX_VERSION:
|
||||
r = self.serialize_without_witness()
|
||||
r = self.serialize_without_witness(include_rangeproof=True)
|
||||
while len(self.wit.vtxinwit) < len(self.vin):
|
||||
self.wit.vtxinwit.append(CTxInWitness())
|
||||
r += self.wit.serialize()
|
||||
|
|
|
@ -671,7 +671,6 @@ def LegacySignatureHash(script, txTo, inIdx, hashtype):
|
|||
# Note that this corresponds to sigversion == 1 in EvalScript, which is used
|
||||
# for version 0 witnesses.
|
||||
def SegwitV0SignatureHash(script, txTo, inIdx, hashtype, amount):
|
||||
|
||||
hashPrevouts = 0
|
||||
hashSequence = 0
|
||||
hashOutputs = 0
|
||||
|
@ -703,7 +702,12 @@ def SegwitV0SignatureHash(script, txTo, inIdx, hashtype, amount):
|
|||
ss += ser_uint256(hashSequence)
|
||||
ss += txTo.vin[inIdx].prevout.serialize()
|
||||
ss += ser_string(script)
|
||||
ss += struct.pack("<q", amount)
|
||||
if isinstance(amount, int):
|
||||
ss += struct.pack("<q", amount)
|
||||
elif isinstance(amount, bytes):
|
||||
ss += amount
|
||||
else:
|
||||
raise ValueError('Unknown amount type')
|
||||
ss += struct.pack("<I", txTo.vin[inIdx].nSequence)
|
||||
ss += ser_uint256(hashOutputs)
|
||||
ss += struct.pack("<i", txTo.nLockTime)
|
||||
|
|
|
@ -301,9 +301,9 @@ class XmrSwap(Base):
|
|||
kbsl_dleag = sa.Column(sa.LargeBinary)
|
||||
kbsf_dleag = sa.Column(sa.LargeBinary)
|
||||
|
||||
vkbv = sa.Column(sa.LargeBinary)
|
||||
pkbv = sa.Column(sa.LargeBinary)
|
||||
pkbs = sa.Column(sa.LargeBinary)
|
||||
vkbv = sa.Column(sa.LargeBinary) # chain b view private key
|
||||
pkbv = sa.Column(sa.LargeBinary) # chain b view public key
|
||||
pkbs = sa.Column(sa.LargeBinary) # chain b view spend key
|
||||
|
||||
a_lock_tx = sa.Column(sa.LargeBinary)
|
||||
a_lock_tx_script = sa.Column(sa.LargeBinary)
|
||||
|
|
|
@ -66,7 +66,7 @@ def value_or_none(v):
|
|||
|
||||
|
||||
def getCoinName(c):
|
||||
if c == Coins.PART_ANON:
|
||||
if c == Coins.PART_ANON or c == Coins.PART_BLIND:
|
||||
return chainparams[Coins.PART]['name'].capitalize() + 'Anon'
|
||||
return chainparams[c]['name'].capitalize()
|
||||
|
||||
|
|
|
@ -13,16 +13,16 @@ from io import BytesIO
|
|||
from basicswap.contrib.test_framework import segwit_addr
|
||||
|
||||
from .util import (
|
||||
dumpj,
|
||||
toWIF,
|
||||
ensure,
|
||||
make_int,
|
||||
b58encode,
|
||||
decodeAddress,
|
||||
decodeScriptNum,
|
||||
getCompactSizeLen,
|
||||
SerialiseNumCompact,
|
||||
dumpj,
|
||||
format_amount,
|
||||
make_int,
|
||||
toWIF,
|
||||
assert_cond,
|
||||
decodeAddress)
|
||||
getWitnessElementLen)
|
||||
from coincurve.keys import (
|
||||
PrivateKey,
|
||||
PublicKey)
|
||||
|
@ -50,11 +50,9 @@ from .contrib.test_framework.messages import (
|
|||
FromHex)
|
||||
|
||||
from .contrib.test_framework.script import (
|
||||
CScript,
|
||||
CScriptOp,
|
||||
CScript, CScriptOp,
|
||||
OP_IF, OP_ELSE, OP_ENDIF,
|
||||
OP_0,
|
||||
OP_2,
|
||||
OP_0, OP_2,
|
||||
OP_CHECKSIG,
|
||||
OP_CHECKMULTISIG,
|
||||
OP_CHECKSEQUENCEVERIFY,
|
||||
|
@ -67,7 +65,7 @@ from .basicswap_util import (
|
|||
SEQUENCE_LOCK_BLOCKS,
|
||||
SEQUENCE_LOCK_TIME)
|
||||
|
||||
from .chainparams import CoinInterface, Coins, chainparams
|
||||
from .chainparams import CoinInterface, Coins
|
||||
from .rpc import make_rpc_func
|
||||
|
||||
|
||||
|
@ -76,6 +74,10 @@ SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
|
|||
SEQUENCE_LOCKTIME_MASK = 0x0000ffff
|
||||
|
||||
|
||||
def ensure_op(v, err_string='Bad opcode'):
|
||||
ensure(v, err_string)
|
||||
|
||||
|
||||
def findOutput(tx, script_pk):
|
||||
for i in range(len(tx.vout)):
|
||||
if tx.vout[i].scriptPubKey == script_pk:
|
||||
|
@ -188,7 +190,7 @@ class BTCInterface(CoinInterface):
|
|||
return self.rpc_callback('getblockheader', [block_hash])
|
||||
|
||||
def initialiseWallet(self, key_bytes):
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
key_wif = toWIF(wif_prefix, key_bytes)
|
||||
|
||||
try:
|
||||
|
@ -211,7 +213,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
chain_synced = round(blockchaininfo['verificationprogress'], 3)
|
||||
if chain_synced < 1.0:
|
||||
raise ValueError('{} chain isn\'t synced.'.format(self.chain_name()))
|
||||
raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
|
||||
|
||||
block_hash = best_block
|
||||
while True:
|
||||
|
@ -241,24 +243,35 @@ class BTCInterface(CoinInterface):
|
|||
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
||||
|
||||
def decodeAddress(self, address):
|
||||
bech32_prefix = chainparams[self.coin_type()][self._network]['hrp']
|
||||
bech32_prefix = self.chainparams_network()['hrp']
|
||||
if address.startswith(bech32_prefix):
|
||||
return bytes(segwit_addr.decode(bech32_prefix, address)[1])
|
||||
return decodeAddress(address)[1:]
|
||||
|
||||
def pubkey_to_segwit_address(self, pk):
|
||||
bech32_prefix = chainparams[self.coin_type()][self._network]['hrp']
|
||||
bech32_prefix = self.chainparams_network()['hrp']
|
||||
version = 0
|
||||
pkh = hash160(pk)
|
||||
return segwit_addr.encode(bech32_prefix, version, pkh)
|
||||
|
||||
def pubkey_to_address(self, pk):
|
||||
assert(len(pk) == 33)
|
||||
prefix = chainparams[self.coin_type()][self._network]['pubkey_address']
|
||||
data = bytes((prefix,)) + hash160(pk)
|
||||
def pkh_to_address(self, pkh):
|
||||
# pkh is hash160(pk)
|
||||
assert(len(pkh) == 20)
|
||||
prefix = self.chainparams_network()['pubkey_address']
|
||||
data = bytes((prefix,)) + pkh
|
||||
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||
return b58encode(data + checksum[0:4])
|
||||
|
||||
def encode_p2wsh(self, script):
|
||||
bech32_prefix = self.chainparams_network()['hrp']
|
||||
version = 0
|
||||
program = script[2:] # strip version and length
|
||||
return segwit_addr.encode(bech32_prefix, version, program)
|
||||
|
||||
def pubkey_to_address(self, pk):
|
||||
assert(len(pk) == 33)
|
||||
return self.pkh_to_address(hash160(pk))
|
||||
|
||||
def getNewSecretKey(self):
|
||||
return getSecretInt()
|
||||
|
||||
|
@ -299,19 +312,19 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
def extractScriptLockScriptValues(self, script_bytes):
|
||||
script_len = len(script_bytes)
|
||||
assert_cond(script_len == 71, 'Bad script length')
|
||||
ensure(script_len == 71, 'Bad script length')
|
||||
o = 0
|
||||
assert_cond(script_bytes[o] == OP_2)
|
||||
assert_cond(script_bytes[o + 1] == 33)
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == 33)
|
||||
o += 2
|
||||
pk1 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
assert_cond(script_bytes[o] == 33)
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk2 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
assert_cond(script_bytes[o] == OP_2)
|
||||
assert_cond(script_bytes[o + 1] == OP_CHECKMULTISIG)
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
|
||||
|
||||
return pk1, pk2
|
||||
|
||||
|
@ -321,7 +334,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)])
|
||||
|
||||
def createScriptLockTx(self, value, Kal, Kaf):
|
||||
def createScriptLockTx(self, value, Kal, Kaf, vkbv=None):
|
||||
script = self.genScriptLockTxScript(Kal, Kaf)
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
|
@ -329,34 +342,37 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx.serialize(), script
|
||||
|
||||
def fundScriptLockTx(self, tx_bytes, feerate, vkbv=None):
|
||||
return self.fundTx(tx_bytes, feerate)
|
||||
|
||||
def extractScriptLockRefundScriptValues(self, script_bytes):
|
||||
script_len = len(script_bytes)
|
||||
assert_cond(script_len > 73, 'Bad script length')
|
||||
assert_cond(script_bytes[0] == OP_IF)
|
||||
assert_cond(script_bytes[1] == OP_2)
|
||||
assert_cond(script_bytes[2] == 33)
|
||||
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]
|
||||
assert_cond(script_bytes[36] == 33)
|
||||
ensure_op(script_bytes[36] == 33)
|
||||
pk2 = script_bytes[37: 37 + 33]
|
||||
assert_cond(script_bytes[70] == OP_2)
|
||||
assert_cond(script_bytes[71] == OP_CHECKMULTISIG)
|
||||
assert_cond(script_bytes[72] == OP_ELSE)
|
||||
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
|
||||
|
||||
assert_cond(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||
assert_cond(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||
o += 1
|
||||
assert_cond(script_bytes[o] == OP_DROP)
|
||||
ensure_op(script_bytes[o] == OP_DROP)
|
||||
o += 1
|
||||
assert_cond(script_bytes[o] == 33)
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk3 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
assert_cond(script_bytes[o] == OP_CHECKSIG)
|
||||
ensure_op(script_bytes[o] == OP_CHECKSIG)
|
||||
o += 1
|
||||
assert_cond(script_bytes[o] == OP_ENDIF)
|
||||
ensure_op(script_bytes[o] == OP_ENDIF)
|
||||
|
||||
return pk1, pk2, csv_val, pk3
|
||||
|
||||
|
@ -373,30 +389,28 @@ class BTCInterface(CoinInterface):
|
|||
Kaf_enc, CScriptOp(OP_CHECKSIG),
|
||||
CScriptOp(OP_ENDIF)])
|
||||
|
||||
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate):
|
||||
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
|
||||
tx_lock = CTransaction()
|
||||
tx_lock = FromHex(tx_lock, tx_lock_bytes.hex())
|
||||
|
||||
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
|
||||
locked_n = findOutput(tx_lock, output_script)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock.vout[locked_n].nValue
|
||||
|
||||
tx_lock.rehash()
|
||||
tx_lock_hash_int = tx_lock.sha256
|
||||
tx_lock_id_int = tx_lock.sha256
|
||||
|
||||
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_hash_int, locked_n), nSequence=lock1_value))
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), nSequence=lock1_value))
|
||||
tx.vout.append(self.txoType()(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()])))
|
||||
|
||||
witness_bytes = len(script_lock)
|
||||
witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 2 # 2 empty witness stack values
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||
pay_fee = int(tx_fee_rate * vsize // 1000)
|
||||
tx.vout[0].nValue = locked_coin - pay_fee
|
||||
|
||||
tx.rehash()
|
||||
|
@ -405,7 +419,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx.serialize(), refund_script, tx.vout[0].nValue
|
||||
|
||||
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate):
|
||||
def createScriptLockRefundSpendTx(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
|
||||
|
@ -414,7 +428,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
|
||||
locked_n = findOutput(tx_lock_refund, output_script)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||
|
||||
tx_lock_refund.rehash()
|
||||
|
@ -426,12 +440,10 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
|
||||
|
||||
witness_bytes = len(script_lock_refund)
|
||||
witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||
pay_fee = int(tx_fee_rate * vsize // 1000)
|
||||
tx.vout[0].nValue = locked_coin - pay_fee
|
||||
|
||||
tx.rehash()
|
||||
|
@ -440,14 +452,15 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx.serialize()
|
||||
|
||||
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate):
|
||||
def createScriptLockRefundSpendToFTx(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 = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
|
||||
locked_n = findOutput(tx_lock_refund, output_script)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
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)
|
||||
|
@ -461,12 +474,10 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
|
||||
|
||||
witness_bytes = len(script_lock_refund)
|
||||
witness_bytes += 74 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 1 # 1 empty stack value
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||
pay_fee = int(tx_fee_rate * vsize // 1000)
|
||||
tx.vout[0].nValue = locked_coin - pay_fee
|
||||
|
||||
tx.rehash()
|
||||
|
@ -475,29 +486,26 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx.serialize()
|
||||
|
||||
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate):
|
||||
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None):
|
||||
tx_lock = self.loadTx(tx_lock_bytes)
|
||||
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
|
||||
locked_n = findOutput(tx_lock, output_script)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock.vout[locked_n].nValue
|
||||
|
||||
tx_lock.rehash()
|
||||
tx_lock_hash_int = tx_lock.sha256
|
||||
tx_lock_id_int = tx_lock.sha256
|
||||
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_hash_int, locked_n)))
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n)))
|
||||
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
|
||||
|
||||
witness_bytes = len(script_lock)
|
||||
witness_bytes += 33 # sv, size
|
||||
witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||
pay_fee = int(tx_fee_rate * vsize // 1000)
|
||||
tx.vout[0].nValue = locked_coin - pay_fee
|
||||
|
||||
tx.rehash()
|
||||
|
@ -510,7 +518,7 @@ class BTCInterface(CoinInterface):
|
|||
swap_value,
|
||||
Kal, Kaf,
|
||||
feerate,
|
||||
check_lock_tx_inputs):
|
||||
check_lock_tx_inputs, vkbv=None):
|
||||
# Verify:
|
||||
#
|
||||
|
||||
|
@ -519,26 +527,28 @@ class BTCInterface(CoinInterface):
|
|||
# Check fee is reasonable
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
self._log.info('Verifying lock tx: {}.'.format(b2h(tx_hash)))
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock tx: {}.'.format(b2h(txid)))
|
||||
|
||||
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
assert_cond(tx.nLockTime == 0, 'Bad nLockTime')
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores
|
||||
|
||||
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||
locked_n = findOutput(tx, script_pk)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx.vout[locked_n].nValue
|
||||
|
||||
assert_cond(locked_coin == swap_value, 'Bad locked value')
|
||||
# Check value
|
||||
ensure(locked_coin == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script and values
|
||||
# Check script
|
||||
A, B = self.extractScriptLockScriptValues(script_out)
|
||||
assert_cond(A == Kal, 'Bad script pubkey')
|
||||
assert_cond(B == Kaf, 'Bad script pubkey')
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
|
||||
if check_lock_tx_inputs:
|
||||
# Check that inputs are unspent and verify fee rate
|
||||
# TODO: Check that inputs are unspent
|
||||
# Verify fee rate
|
||||
inputs_value = 0
|
||||
add_bytes = 0
|
||||
add_witness_bytes = getCompactSizeLen(len(tx.vin))
|
||||
|
@ -562,7 +572,7 @@ class BTCInterface(CoinInterface):
|
|||
assert(fee_paid > 0)
|
||||
|
||||
vsize = self.getTxVSize(tx, add_bytes, add_witness_bytes)
|
||||
fee_rate_paid = fee_paid * 1000 / vsize
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
|
||||
self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid)
|
||||
|
||||
|
@ -570,97 +580,93 @@ class BTCInterface(CoinInterface):
|
|||
self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate)
|
||||
# TODO: Display warning to user
|
||||
|
||||
return tx_hash, locked_n
|
||||
return txid, locked_n
|
||||
|
||||
def verifyLockRefundTx(self, tx_bytes, script_out,
|
||||
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
|
||||
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||
Kal, Kaf, csv_val_expect, swap_value, feerate):
|
||||
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)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
self._log.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash)))
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid)))
|
||||
|
||||
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
assert_cond(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
assert_cond(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence')
|
||||
assert_cond(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||
assert_cond(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch')
|
||||
ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence')
|
||||
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||
ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch')
|
||||
|
||||
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
|
||||
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||
locked_n = findOutput(tx, script_pk)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx.vout[locked_n].nValue
|
||||
|
||||
# Check script and values
|
||||
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||
assert_cond(A == Kal, 'Bad script pubkey')
|
||||
assert_cond(B == Kaf, 'Bad script pubkey')
|
||||
assert_cond(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
assert_cond(C == Kaf, 'Bad script pubkey')
|
||||
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)
|
||||
|
||||
witness_bytes = len(prevout_script)
|
||||
witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 2 # 2 empty witness stack values
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
fee_rate_paid = fee_paid * 1000 / vsize
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
|
||||
self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid)
|
||||
|
||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||
raise ValueError('Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return tx_hash, locked_coin
|
||||
return txid, locked_coin, locked_n
|
||||
|
||||
def verifyLockRefundSpendTx(self, tx_bytes,
|
||||
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
|
||||
lock_refund_tx_id, prevout_script,
|
||||
Kal,
|
||||
prevout_value, feerate):
|
||||
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)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash)))
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid)))
|
||||
|
||||
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
assert_cond(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
assert_cond(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||
assert_cond(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||
assert_cond(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch')
|
||||
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||
ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch')
|
||||
|
||||
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
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)
|
||||
assert_cond(locked_n is not None, 'Output not found in lock refund spend tx')
|
||||
ensure(locked_n is not None, 'Output not found in lock refund spend tx')
|
||||
'''
|
||||
tx_value = tx.vout[0].nValue
|
||||
|
||||
fee_paid = prevout_value - tx_value
|
||||
assert(fee_paid > 0)
|
||||
|
||||
witness_bytes = len(prevout_script)
|
||||
witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
fee_rate_paid = fee_paid * 1000 / vsize
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
|
||||
self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid)
|
||||
|
||||
|
@ -671,45 +677,43 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
def verifyLockSpendTx(self, tx_bytes,
|
||||
lock_tx_bytes, lock_tx_script,
|
||||
a_pkhash_f, feerate):
|
||||
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)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
self._log.info('Verifying lock spend tx: {}.'.format(b2h(tx_hash)))
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid)))
|
||||
|
||||
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
assert_cond(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
lock_tx = self.loadTx(lock_tx_bytes)
|
||||
lock_tx_id = self.getTxHash(lock_tx)
|
||||
lock_tx_id = self.getTxid(lock_tx)
|
||||
|
||||
output_script = CScript([OP_0, hashlib.sha256(lock_tx_script).digest()])
|
||||
locked_n = findOutput(lock_tx, output_script)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = lock_tx.vout[locked_n].nValue
|
||||
|
||||
assert_cond(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||
assert_cond(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||
assert_cond(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
|
||||
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||
ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
|
||||
|
||||
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
p2wpkh = self.getScriptForPubkeyHash(a_pkhash_f)
|
||||
assert_cond(tx.vout[0].scriptPubKey == p2wpkh, 'Bad output destination')
|
||||
ensure(tx.vout[0].scriptPubKey == 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].nValue
|
||||
assert(fee_paid > 0)
|
||||
|
||||
witness_bytes = len(lock_tx_script)
|
||||
witness_bytes += 33 # sv, size
|
||||
witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate
|
||||
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
fee_rate_paid = fee_paid * 1000 / vsize
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
|
||||
self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_rate_paid)
|
||||
|
||||
|
@ -718,30 +722,30 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return True
|
||||
|
||||
def signTx(self, key_bytes, tx_bytes, prevout_n, prevout_script, prevout_value):
|
||||
def signTx(self, key_bytes, tx_bytes, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
|
||||
eck = PrivateKey(key_bytes)
|
||||
return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,))
|
||||
|
||||
def signTxOtVES(self, key_sign, pubkey_encrypt, tx_bytes, prevout_n, prevout_script, prevout_value):
|
||||
def signTxOtVES(self, key_sign, pubkey_encrypt, tx_bytes, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
|
||||
return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
|
||||
|
||||
def verifyTxOtVES(self, tx_bytes, ct, Ks, Ke, prevout_n, prevout_script, prevout_value):
|
||||
def verifyTxOtVES(self, tx_bytes, ct, Ks, Ke, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct)
|
||||
|
||||
def decryptOtVES(self, k, esig):
|
||||
return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
|
||||
|
||||
def verifyTxSig(self, tx_bytes, sig, K, prevout_n, prevout_script, prevout_value):
|
||||
def verifyTxSig(self, tx_bytes, sig, K, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
|
||||
pubkey = PublicKey(K)
|
||||
return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte
|
||||
|
@ -751,7 +755,7 @@ class BTCInterface(CoinInterface):
|
|||
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||
|
||||
def fundTx(self, tx, feerate):
|
||||
feerate_str = format_amount(feerate, self.exp())
|
||||
feerate_str = self.format_amount(feerate)
|
||||
# TODO: unlock unspents if bid cancelled
|
||||
options = {
|
||||
'lockUnspents': True,
|
||||
|
@ -784,7 +788,9 @@ class BTCInterface(CoinInterface):
|
|||
tx.deserialize(BytesIO(tx_bytes))
|
||||
return tx
|
||||
|
||||
def getTxHash(self, tx):
|
||||
def getTxid(self, tx):
|
||||
if isinstance(tx, str):
|
||||
tx = bytes.fromhex(tx)
|
||||
if isinstance(tx, bytes):
|
||||
tx = self.loadTx(tx)
|
||||
tx.rehash()
|
||||
|
@ -829,6 +835,11 @@ class BTCInterface(CoinInterface):
|
|||
tx.wit.vtxinwit[0].scriptWitness.stack = stack
|
||||
return tx.serialize()
|
||||
|
||||
def stripTxSignature(self, tx_bytes):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
tx.wit.vtxinwit.clear()
|
||||
return tx.serialize()
|
||||
|
||||
def extractLeaderSig(self, tx_bytes):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
||||
|
@ -851,7 +862,7 @@ class BTCInterface(CoinInterface):
|
|||
b_lock_tx = self.createBLockTx(Kbs, output_amount)
|
||||
|
||||
b_lock_tx = self.fundTx(b_lock_tx, feerate)
|
||||
b_lock_tx_id = self.getTxHash(b_lock_tx)
|
||||
b_lock_tx_id = self.getTxid(b_lock_tx)
|
||||
b_lock_tx = self.signTxWithWallet(b_lock_tx)
|
||||
|
||||
return self.publishTx(b_lock_tx)
|
||||
|
@ -881,7 +892,6 @@ class BTCInterface(CoinInterface):
|
|||
return None
|
||||
|
||||
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed):
|
||||
|
||||
raw_dest = self.getPkDest(Kbs)
|
||||
|
||||
for i in range(20):
|
||||
|
@ -899,9 +909,36 @@ class BTCInterface(CoinInterface):
|
|||
return False
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
print('TODO: spendBLockTx')
|
||||
raise ValueError('TODO')
|
||||
|
||||
def getOutput(self, txid, dest_script, expect_value):
|
||||
def getLockTxHeight(self, txid, dest_script, bid_amount, xmr_swap):
|
||||
rv = None
|
||||
p2wsh_addr = self.encode_p2wsh(dest_script)
|
||||
|
||||
addr_info = self.rpc_callback('getaddressinfo', [p2wsh_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
ro = self.rpc_callback('importaddress', [p2wsh_addr, 'bid', False])
|
||||
self._log.info('Imported watch-only addr: {}'.format(p2wsh_addr))
|
||||
self._log.info('Rescanning chain from height: {}'.format(xmr_swap.start_chain_a_height))
|
||||
self.rpc_callback('rescanblockchain', [xmr_swap.start_chain_a_height])
|
||||
|
||||
try:
|
||||
tx = self.rpc_callback('gettransaction', [txid.hex()])
|
||||
|
||||
block_height = 0
|
||||
if 'blockhash' in tx:
|
||||
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
|
||||
block_height = block_header['height']
|
||||
|
||||
rv = {
|
||||
'depth': 0 if 'confirmations' not in tx else tx['confirmations'],
|
||||
'height': block_height}
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return rv
|
||||
|
||||
def getOutput(self, txid, dest_script, expect_value, xmr_swap=None):
|
||||
# TODO: Use getrawtransaction if txindex is active
|
||||
utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
|
||||
if 'height' in utxos: # chain_height not returned by v18 codebase
|
||||
|
@ -942,7 +979,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
def verifyMessage(self, address, message, signature, message_magic=None) -> bool:
|
||||
if message_magic is None:
|
||||
message_magic = chainparams[self.coin_type()]['message_magic']
|
||||
message_magic = self.chainparams_network()['message_magic']
|
||||
|
||||
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
|
||||
message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest()
|
||||
|
@ -961,7 +998,51 @@ class BTCInterface(CoinInterface):
|
|||
return True if address_hash == pubkey_hash else False
|
||||
|
||||
def showLockTransfers(self, Kbv, Kbs):
|
||||
return 'Unimplemented'
|
||||
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):
|
||||
return [
|
||||
b''.hex(),
|
||||
bytes(72).hex(),
|
||||
bytes(72).hex(),
|
||||
bytes(len(script)).hex()
|
||||
]
|
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script):
|
||||
return [
|
||||
b''.hex(),
|
||||
bytes(72).hex(),
|
||||
bytes(72).hex(),
|
||||
bytes((1,)).hex(),
|
||||
bytes(len(script)).hex()
|
||||
]
|
||||
|
||||
def getScriptLockRefundSwipeTxDummyWitness(self, script):
|
||||
return [
|
||||
bytes(72).hex(),
|
||||
b''.hex(),
|
||||
bytes(len(script)).hex()
|
||||
]
|
||||
|
||||
def getWitnessStackSerialisedLength(self, witness_stack):
|
||||
length = getCompactSizeLen(len(witness_stack))
|
||||
for e in witness_stack:
|
||||
length += getWitnessElementLen(len(e) // 2) # hex -> bytes
|
||||
|
||||
# See core SerializeTransaction
|
||||
length += 32 + 4 + 1 + 4 # vinDummy
|
||||
length += 1 # flags
|
||||
return length
|
||||
|
||||
|
||||
def testBTCInterface():
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import hashlib
|
||||
import basicswap.contrib.segwit_addr as segwit_addr
|
||||
from enum import IntEnum
|
||||
|
||||
from .contrib.test_framework.messages import (
|
||||
|
@ -12,16 +14,20 @@ from .contrib.test_framework.messages import (
|
|||
)
|
||||
from .contrib.test_framework.script import (
|
||||
CScript,
|
||||
OP_0,
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
)
|
||||
from .ecc_util import i2b
|
||||
|
||||
from .util import (
|
||||
encodeStealthAddress,
|
||||
toWIF,
|
||||
ensure,
|
||||
make_int)
|
||||
from .basicswap_util import (
|
||||
TemporaryError)
|
||||
make_int,
|
||||
getP2WSH,
|
||||
TemporaryError,
|
||||
getCompactSizeLen,
|
||||
encodeStealthAddress,
|
||||
getWitnessElementLen)
|
||||
from .chainparams import Coins, chainparams
|
||||
from .interface_btc import BTCInterface
|
||||
|
||||
|
@ -98,12 +104,504 @@ class PARTInterface(BTCInterface):
|
|||
|
||||
return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey)
|
||||
|
||||
def getWitnessStackSerialisedLength(self, witness_stack):
|
||||
length = getCompactSizeLen(len(witness_stack))
|
||||
for e in witness_stack:
|
||||
length += getWitnessElementLen(len(e) // 2) # hex -> bytes
|
||||
return length
|
||||
|
||||
|
||||
class PARTInterfaceBlind(PARTInterface):
|
||||
@staticmethod
|
||||
def balance_type():
|
||||
return BalanceTypes.BLIND
|
||||
|
||||
def encodeSegwitP2WSH(self, p2wsh):
|
||||
return segwit_addr.encode(self.chainparams_network()['hrp'], 0, p2wsh[2:])
|
||||
|
||||
def getScriptLockTxNonce(self, data):
|
||||
return hashlib.sha256(data + bytes('locktx', 'utf-8')).digest()
|
||||
|
||||
def getScriptLockRefundTxNonce(self, data):
|
||||
return hashlib.sha256(data + bytes('lockrefundtx', 'utf-8')).digest()
|
||||
|
||||
def findOutputByNonce(self, tx_obj, nonce):
|
||||
blinded_info = None
|
||||
output_n = None
|
||||
for txo in tx_obj['vout']:
|
||||
if txo['type'] != 'blind':
|
||||
continue
|
||||
try:
|
||||
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
output_n = txo['n']
|
||||
|
||||
self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
break
|
||||
except Exception as e:
|
||||
self._log.debug('Searching for locked output: {}'.format(str(e)))
|
||||
continue
|
||||
# Should not be possible for commitment not to match
|
||||
v = self.rpc_callback('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
|
||||
ensure(v['result'] is True, 'verifycommitment failed')
|
||||
return output_n, blinded_info
|
||||
|
||||
def createScriptLockTx(self, value, Kal, Kaf, vkbv):
|
||||
script = self.genScriptLockTxScript(Kal, Kaf)
|
||||
|
||||
# Nonce is derived from vkbv, ephemeral_key isn't used
|
||||
ephemeral_key = i2b(self.getNewSecretKey())
|
||||
ephemeral_pubkey = self.getPubkey(ephemeral_key)
|
||||
assert(len(ephemeral_pubkey) == 33)
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
p2wsh_addr = self.encodeSegwitP2WSH(getP2WSH(script))
|
||||
inputs = []
|
||||
outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
|
||||
tx_bytes = bytes.fromhex(rv['hex'])
|
||||
return tx_bytes, script
|
||||
|
||||
def fundScriptLockTx(self, tx_bytes, feerate, vkbv):
|
||||
feerate_str = self.format_amount(feerate)
|
||||
# TODO: unlock unspents if bid cancelled
|
||||
|
||||
tx_hex = tx_bytes.hex()
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
|
||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx_hex])
|
||||
|
||||
assert(len(tx_obj['vout']) == 1)
|
||||
txo = tx_obj['vout'][0]
|
||||
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
|
||||
outputs_info = {0: {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'nonce': nonce.hex()}}
|
||||
|
||||
options = {
|
||||
'lockUnspents': True,
|
||||
'feeRate': feerate_str,
|
||||
}
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||
assert(self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
|
||||
# Nonce is derived from vkbv, ephemeral_key isn't used
|
||||
ephemeral_key = i2b(self.getNewSecretKey())
|
||||
ephemeral_pubkey = self.getPubkey(ephemeral_key)
|
||||
assert(len(ephemeral_pubkey) == 33)
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
output_nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||
|
||||
# Find the output of the lock tx to spend
|
||||
spend_n, input_blinded_info = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||
ensure(spend_n is not None, 'Output not found in tx')
|
||||
|
||||
locked_coin = input_blinded_info['amount']
|
||||
tx_lock_id = lock_tx_obj['txid']
|
||||
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
|
||||
p2wsh_addr = self.encodeSegwitP2WSH(getP2WSH(refund_script))
|
||||
|
||||
inputs = [{'txid': tx_lock_id, 'vout': spend_n, 'sequence': lock1_value, 'blindingfactor': input_blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': locked_coin, 'address': p2wsh_addr, 'nonce': output_nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
lock_refund_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||
|
||||
# Use a junk change pubkey to avoid adding unused keys to the wallet
|
||||
zero_change_key = i2b(self.getNewSecretKey())
|
||||
zero_change_pubkey = self.getPubkey(zero_change_key)
|
||||
inputs_info = {'0': {'value': input_blinded_info['amount'], 'blind': input_blinded_info['blind'], 'witnessstack': dummy_witness_stack}}
|
||||
outputs_info = rv['amounts']
|
||||
options = {
|
||||
'changepubkey': zero_change_pubkey.hex(),
|
||||
'feeRate': self.format_amount(tx_fee_rate),
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_refund_tx_hex = rv['hex']
|
||||
|
||||
for vout, txo in rv['output_amounts'].items():
|
||||
if txo['value'] > 0:
|
||||
refunded_value = txo['value']
|
||||
|
||||
return bytes.fromhex(lock_refund_tx_hex), refund_script, refunded_value
|
||||
|
||||
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv):
|
||||
# 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
|
||||
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
# Nonce is derived from vkbv
|
||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||
|
||||
# Find the output of the lock refund tx to spend
|
||||
spend_n, input_blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(spend_n is not None, 'Output not found in tx')
|
||||
|
||||
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
||||
addr_out = self.pkh_to_address(pkh_refund_to)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
|
||||
output_pubkey_hex = addr_info['pubkey']
|
||||
|
||||
# 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
|
||||
|
||||
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': 0, 'blindingfactor': input_blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
lock_refund_spend_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund)
|
||||
|
||||
# Use a junk change pubkey to avoid adding unused keys to the wallet
|
||||
zero_change_key = i2b(self.getNewSecretKey())
|
||||
zero_change_pubkey = self.getPubkey(zero_change_key)
|
||||
inputs_info = {'0': {'value': input_blinded_info['amount'], 'blind': input_blinded_info['blind'], 'witnessstack': dummy_witness_stack}}
|
||||
outputs_info = rv['amounts']
|
||||
options = {
|
||||
'changepubkey': zero_change_pubkey.hex(),
|
||||
'feeRate': self.format_amount(tx_fee_rate),
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_refund_spend_tx_hex = rv['hex']
|
||||
|
||||
return bytes.fromhex(lock_refund_spend_tx_hex)
|
||||
|
||||
def verifyLockTx(self, tx_bytes, script_out,
|
||||
swap_value,
|
||||
Kal, Kaf,
|
||||
feerate,
|
||||
check_lock_tx_inputs, vkbv):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_txid_hex = lock_tx_obj['txid']
|
||||
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
|
||||
|
||||
ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||
ensure(lock_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||
|
||||
# Find the output of the lock tx to verify
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
lock_output_n, blinded_info = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||
ensure(lock_output_n is not None, 'Output not found in tx')
|
||||
|
||||
# Check value
|
||||
locked_txo_value = make_int(blinded_info['amount'])
|
||||
ensure(locked_txo_value == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script
|
||||
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)
|
||||
ensure(A == Kal, 'Bad script leader pubkey')
|
||||
ensure(B == Kaf, 'Bad script follower pubkey')
|
||||
|
||||
# TODO: Check that inputs are unspent, rangeproofs and commitments sum
|
||||
# Verify fee rate
|
||||
vsize = lock_tx_obj['vsize']
|
||||
fee_paid = make_int(lock_tx_obj['vout'][0]['ct_fee'])
|
||||
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
|
||||
self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_txo_value, vsize, 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 bytes.fromhex(lock_txid_hex), lock_output_n
|
||||
|
||||
def verifyLockRefundTx(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):
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_refund_txid_hex = lock_refund_tx_obj['txid']
|
||||
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
|
||||
|
||||
ensure(lock_refund_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||
ensure(lock_refund_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||
ensure(len(lock_refund_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
txin = lock_refund_tx_obj['vin'][0]
|
||||
ensure(txin['sequence'] == prevout_seq, 'Bad input nSequence')
|
||||
ensure(txin['scriptSig']['hex'] == '', 'Input scriptsig not empty')
|
||||
ensure(txin['txid'] == prevout_id.hex() and txin['vout'] == prevout_n, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(lock_refund_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
|
||||
|
||||
# Find the output of the lock refund tx to verify
|
||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||
lock_refund_output_n, blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(lock_refund_output_n is not None, 'Output not found in tx')
|
||||
|
||||
lock_refund_txo_value = make_int(blinded_info['amount'])
|
||||
|
||||
# Check script
|
||||
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)
|
||||
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')
|
||||
|
||||
# Check rangeproofs and commitments sum
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||
prevout = lock_tx_obj['vout'][prevout_n]
|
||||
prevtxns = [{'txid': prevout_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
# Check value
|
||||
fee_paid = make_int(lock_refund_tx_obj['vout'][0]['ct_fee'])
|
||||
ensure(swap_value - lock_refund_txo_value == fee_paid, 'Bad output value')
|
||||
|
||||
# Check fee rate
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes)
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
self._log.info('vsize, feerate: %ld, %ld', vsize, fee_rate_paid)
|
||||
|
||||
ensure(self.compareFeeRates(fee_rate_paid, feerate), 'Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n
|
||||
|
||||
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
|
||||
lock_refund_tx_id, prevout_script,
|
||||
Kal,
|
||||
prevout_n, prevout_value, feerate, vkbv):
|
||||
lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
|
||||
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
|
||||
|
||||
ensure(lock_refund_spend_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||
ensure(lock_refund_spend_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||
ensure(len(lock_refund_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
txin = lock_refund_spend_tx_obj['vin'][0]
|
||||
ensure(txin['sequence'] == 0, 'Bad input nSequence')
|
||||
ensure(txin['scriptSig']['hex'] == '', 'Input scriptsig not empty')
|
||||
ensure(txin['txid'] == lock_refund_tx_id.hex() and txin['vout'] == prevout_n, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(lock_refund_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
|
||||
|
||||
# Leader picks output destinations
|
||||
# Follower is not concerned with them as they pay to leader
|
||||
|
||||
# Check rangeproofs and commitments sum
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [lock_refund_tx_bytes.hex()])
|
||||
prevout = lock_refund_tx_obj['vout'][prevout_n]
|
||||
prevtxns = [{'txid': lock_refund_tx_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
# Check fee rate
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes)
|
||||
fee_paid = make_int(lock_refund_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
ensure(self.compareFeeRates(fee_rate_paid, feerate), 'Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return True
|
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
|
||||
nonce = self.getScriptLockTxNonce(xmr_swap.vkbv)
|
||||
output_n, _ = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
return bytes.fromhex(lock_tx_obj['vout'][output_n]['valueCommitment'])
|
||||
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
||||
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
return bytes.fromhex(lock_refund_tx_obj['vout'][output_n]['valueCommitment'])
|
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap):
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
||||
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
return output_n
|
||||
|
||||
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||
lock_txid_hex = lock_tx_obj['txid']
|
||||
|
||||
ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||
ensure(lock_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||
|
||||
# Find the output of the lock tx to verify
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
spend_n, blinded_info = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||
ensure(spend_n is not None, 'Output not found in tx')
|
||||
|
||||
addr_out = self.pubkey_to_address(pk_dest)
|
||||
|
||||
inputs = [{'txid': lock_txid_hex, 'vout': spend_n, 'sequence': 0, 'blindingfactor': blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': blinded_info['amount'], 'address': addr_out, 'pubkey': pk_dest.hex()}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
lock_spend_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||
|
||||
# Use a junk change pubkey to avoid adding unused keys to the wallet
|
||||
zero_change_key = i2b(self.getNewSecretKey())
|
||||
zero_change_pubkey = self.getPubkey(zero_change_key)
|
||||
inputs_info = {'0': {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'witnessstack': dummy_witness_stack}}
|
||||
outputs_info = rv['amounts']
|
||||
options = {
|
||||
'changepubkey': zero_change_pubkey.hex(),
|
||||
'feeRate': self.format_amount(tx_fee_rate),
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_spend_tx_hex = rv['hex']
|
||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [lock_spend_tx_hex])
|
||||
|
||||
vsize = lock_spend_tx_obj['vsize']
|
||||
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
actual_tx_fee_rate = pay_fee * 1000 // vsize
|
||||
self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||
lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee)
|
||||
|
||||
return bytes.fromhex(lock_spend_tx_hex)
|
||||
|
||||
def verifyLockSpendTx(self, tx_bytes,
|
||||
lock_tx_bytes, lock_tx_script,
|
||||
a_pk_f, feerate, vkbv):
|
||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_spend_txid_hex = lock_spend_tx_obj['txid']
|
||||
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
|
||||
|
||||
ensure(lock_spend_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||
ensure(lock_spend_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||
ensure(len(lock_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||
lock_txid_hex = lock_tx_obj['txid']
|
||||
|
||||
# Find the output of the lock tx to verify
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
spend_n, input_blinded_info = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||
ensure(spend_n is not None, 'Output not found in tx')
|
||||
|
||||
txin = lock_spend_tx_obj['vin'][0]
|
||||
ensure(txin['sequence'] == 0, 'Bad input nSequence')
|
||||
ensure(txin['scriptSig']['hex'] == '', 'Input scriptsig not empty')
|
||||
ensure(txin['txid'] == lock_txid_hex and txin['vout'] == spend_n, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(lock_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
|
||||
|
||||
addr_out = self.pubkey_to_address(a_pk_f)
|
||||
privkey = self.rpc_callback('dumpprivkey', [addr_out])
|
||||
|
||||
# Find output:
|
||||
output_blinded_info = None
|
||||
output_n = None
|
||||
for txo in lock_spend_tx_obj['vout']:
|
||||
if txo['type'] != 'blind':
|
||||
continue
|
||||
try:
|
||||
output_blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
|
||||
output_n = txo['n']
|
||||
break
|
||||
except Exception as e:
|
||||
self._log.debug('Searching for locked output: {}'.format(str(e)))
|
||||
pass
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
|
||||
# Commitment
|
||||
v = self.rpc_callback('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
|
||||
ensure(v['result'] is True, 'verifycommitment failed')
|
||||
|
||||
# Check rangeproofs and commitments sum
|
||||
prevout = lock_tx_obj['vout'][spend_n]
|
||||
prevtxns = [{'txid': lock_txid_hex, 'vout': spend_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
# Check amount
|
||||
fee_paid = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
amount_difference = make_int(input_blinded_info['amount']) - make_int(output_blinded_info['amount'])
|
||||
ensure(fee_paid == amount_difference, 'Invalid output amount')
|
||||
|
||||
# Check fee
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
|
||||
vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes)
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
self._log.info('vsize, feerate: %ld, %ld', vsize, fee_rate_paid)
|
||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||
raise ValueError('Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return True
|
||||
|
||||
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||
|
||||
# Find the output of the lock refund tx to spend
|
||||
spend_n, input_blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(spend_n is not None, 'Output not found in tx')
|
||||
|
||||
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
||||
addr_out = self.pkh_to_address(pkh_dest)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
|
||||
output_pubkey_hex = addr_info['pubkey']
|
||||
|
||||
A, B, lock2_value, C = self.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
|
||||
|
||||
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': lock2_value, 'blindingfactor': input_blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
|
||||
lock_refund_swipe_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund)
|
||||
|
||||
# Use a junk change pubkey to avoid adding unused keys to the wallet
|
||||
zero_change_key = i2b(self.getNewSecretKey())
|
||||
zero_change_pubkey = self.getPubkey(zero_change_key)
|
||||
inputs_info = {'0': {'value': input_blinded_info['amount'], 'blind': input_blinded_info['blind'], 'witnessstack': dummy_witness_stack}}
|
||||
outputs_info = rv['amounts']
|
||||
options = {
|
||||
'changepubkey': zero_change_pubkey.hex(),
|
||||
'feeRate': self.format_amount(tx_fee_rate),
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_refund_swipe_tx_hex = rv['hex']
|
||||
|
||||
return bytes.fromhex(lock_refund_swipe_tx_hex)
|
||||
|
||||
|
||||
class PARTInterfaceAnon(PARTInterface):
|
||||
@staticmethod
|
||||
|
@ -134,7 +632,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
else:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
|
@ -166,7 +664,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
|
|
|
@ -26,8 +26,7 @@ from .util import (
|
|||
ensure,
|
||||
dumpj,
|
||||
make_int,
|
||||
format_amount)
|
||||
from .basicswap_util import (
|
||||
format_amount,
|
||||
TemporaryError)
|
||||
from .rpc_xmr import (
|
||||
make_xmr_rpc_func,
|
||||
|
|
|
@ -22,9 +22,13 @@ decimal_ctx = decimal.Context()
|
|||
decimal_ctx.prec = 20
|
||||
|
||||
|
||||
def assert_cond(v, err='Bad opcode'):
|
||||
class TemporaryError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def ensure(v, err_string):
|
||||
if not v:
|
||||
raise ValueError(err)
|
||||
raise ValueError(err_string)
|
||||
|
||||
|
||||
def toBool(s) -> bool:
|
||||
|
@ -222,6 +226,10 @@ def getCompactSizeLen(v):
|
|||
raise ValueError('Value too large')
|
||||
|
||||
|
||||
def getWitnessElementLen(v):
|
||||
return getCompactSizeLen(v) + v
|
||||
|
||||
|
||||
def SerialiseNumCompact(v):
|
||||
if v < 253:
|
||||
return bytes((v,))
|
||||
|
@ -339,8 +347,3 @@ def encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey):
|
|||
b = bytes((prefix_byte,)) + data
|
||||
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
|
||||
return b58encode(b)
|
||||
|
||||
|
||||
def ensure(passed, err_string):
|
||||
if not passed:
|
||||
raise ValueError(err_string)
|
||||
|
|
|
@ -1,5 +1,36 @@
|
|||
|
||||
0.0.25
|
||||
==============
|
||||
|
||||
- Fix extra 33 bytes in lock spend fee calculation.
|
||||
- XMR swaps use watchonly addresses to save the lock tx to the wallet
|
||||
- Instead of scantxoutset
|
||||
- Add missing check of leader's lock refund tx signature result
|
||||
- Blind part -> XMR swaps are possible:
|
||||
- The sha256 hash of the chain b view private key is used as the nonce for transactions requiring cooperation to sign.
|
||||
- Follower sends a public key in xmr_swap.dest_af.
|
||||
- Verify the rangeproofs and commitments of blinded pre-shared txns.
|
||||
- Add explicit tests for all paths of:
|
||||
- PARTct -> XMR
|
||||
- BTC -> XMR
|
||||
- LTC -> XMR
|
||||
|
||||
|
||||
0.0.24
|
||||
==============
|
||||
|
||||
- Can swap Particl Anon outputs in place of XMR
|
||||
|
||||
|
||||
0.0.23
|
||||
==============
|
||||
|
||||
- Enables private offers
|
||||
|
||||
|
||||
0.0.22
|
||||
==============
|
||||
|
||||
- Improved wallets page
|
||||
- Consistent wallet order
|
||||
- Separated RPC calls into threads.
|
||||
|
@ -7,6 +38,7 @@
|
|||
|
||||
0.0.21
|
||||
==============
|
||||
|
||||
- Raised Particl and Monero daemon versions.
|
||||
- Display shared address on bid page if show more info is enabled.
|
||||
- Added View Lock Wallet Transfers button to bid page.
|
||||
|
@ -14,5 +46,6 @@
|
|||
|
||||
0.0.6
|
||||
==============
|
||||
|
||||
- Experimental support for XMR swaps.
|
||||
- Single direction only, scriptless -> XMR
|
||||
|
|
|
@ -29,6 +29,10 @@ BTC_BASE_PORT = 31792
|
|||
BTC_BASE_RPC_PORT = 32792
|
||||
BTC_BASE_ZMQ_PORT = 33792
|
||||
|
||||
LTC_BASE_PORT = 34792
|
||||
LTC_BASE_RPC_PORT = 35792
|
||||
LTC_BASE_ZMQ_PORT = 36792
|
||||
|
||||
PREFIX_SECRET_KEY_REGTEST = 0x2e
|
||||
|
||||
|
||||
|
|
229
tests/basicswap/test_btc_xmr.py
Normal file
229
tests/basicswap/test_btc_xmr.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import unittest
|
||||
from urllib.request import urlopen
|
||||
|
||||
from basicswap.basicswap import (
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
DebugTypes,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
SEQUENCE_LOCK_BLOCKS,
|
||||
)
|
||||
from basicswap.util import (
|
||||
make_int,
|
||||
format_amount,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
wait_for_bid,
|
||||
wait_for_offer,
|
||||
wait_for_none_active,
|
||||
)
|
||||
|
||||
from .test_xmr import BaseTest, test_delay_event
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Test(BaseTest):
|
||||
__test__ = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if not hasattr(cls, 'test_coin_from'):
|
||||
cls.test_coin_from = Coins.BTC
|
||||
if not hasattr(cls, 'start_ltc_nodes'):
|
||||
cls.start_ltc_nodes = False
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info('Finalising BTC Test')
|
||||
super(Test, cls).tearDownClass()
|
||||
|
||||
def getBalance(self, js_wallets):
|
||||
return float(js_wallets[str(int(self.test_coin_from))]['balance']) + float(js_wallets[str(int(self.test_coin_from))]['unconfirmed'])
|
||||
|
||||
def getXmrBalance(self, js_wallets):
|
||||
return float(js_wallets[str(int(Coins.XMR))]['unconfirmed']) + float(js_wallets[str(int(Coins.XMR))]['balance'])
|
||||
|
||||
def test_01_full_swap(self):
|
||||
logging.info('---------- Test {} to XMR'.format(str(self.test_coin_from)))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_before = self.getBalance(js_0)
|
||||
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
node1_from_before = self.getBalance(js_1)
|
||||
|
||||
js_0_xmr = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/xmr').read())
|
||||
js_1_xmr = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/xmr').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[0].listOffers(filters={'offer_id': offer_id})
|
||||
offer = offers[0]
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
node1_from_after = self.getBalance(js_1)
|
||||
assert(node1_from_after > node1_from_before + (amount_from - 0.05))
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_after = self.getBalance(js_0)
|
||||
# TODO: Discard block rewards
|
||||
# assert(node0_from_after < node0_from_before - amount_from)
|
||||
|
||||
js_0_xmr_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/xmr').read())
|
||||
js_1_xmr_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/xmr').read())
|
||||
|
||||
scale_from = 8
|
||||
amount_to = int((amt_swap * rate_swap) // (10 ** scale_from))
|
||||
amount_to_float = float(format_amount(amount_to, 12))
|
||||
node1_xmr_after = float(js_1_xmr_after['unconfirmed']) + float(js_1_xmr_after['balance'])
|
||||
node1_xmr_before = float(js_1_xmr['unconfirmed']) + float(js_1_xmr['balance'])
|
||||
assert(node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
|
||||
|
||||
def test_02_leader_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(str(self.test_coin_from)))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_before = self.getBalance(js_w0_before)
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_after = self.getBalance(js_w0_after)
|
||||
|
||||
# TODO: Discard block rewards
|
||||
# assert(node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
def test_03_follower_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(str(self.test_coin_from)))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True)
|
||||
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
node1_from_before = self.getBalance(js_w1_before)
|
||||
node1_from_after = self.getBalance(js_w1_after)
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
# TODO: Discard block rewards
|
||||
# assert(node1_from_after - node1_from_before > (amount_from - 0.02))
|
||||
|
||||
wait_for_none_active(test_delay_event, 1800)
|
||||
wait_for_none_active(test_delay_event, 1801)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(str(self.test_coin_from)))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=18)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
node0_from_before = self.getBalance(js_w0_before)
|
||||
node0_from_after = self.getBalance(js_w0_after)
|
||||
|
||||
# TODO: Discard block rewards
|
||||
# assert(node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
node1_xmr_before = self.getXmrBalance(js_w1_before)
|
||||
node1_xmr_after = self.getXmrBalance(js_w1_after)
|
||||
assert(node1_xmr_before - node1_xmr_after < 0.02)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
249
tests/basicswap/test_ltc_xmr.py
Normal file
249
tests/basicswap/test_ltc_xmr.py
Normal file
|
@ -0,0 +1,249 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import unittest
|
||||
from urllib.request import urlopen
|
||||
|
||||
from basicswap.basicswap import (
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
DebugTypes,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
SEQUENCE_LOCK_BLOCKS,
|
||||
)
|
||||
from basicswap.util import (
|
||||
make_int,
|
||||
format_amount,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
wait_for_bid,
|
||||
wait_for_offer,
|
||||
wait_for_none_active,
|
||||
)
|
||||
|
||||
'''
|
||||
# Should work?
|
||||
|
||||
from .test_btc_xmr import Test, test_delay_event
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class TestLTC(Test):
|
||||
__test__ = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.test_coin_from = Coins.LTC
|
||||
cls.start_ltc_nodes = True
|
||||
super(TestLTC, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info('Finalising LTC Test')
|
||||
super(TestLTC, cls).tearDownClass()
|
||||
'''
|
||||
|
||||
from .test_xmr import BaseTest, test_delay_event
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Test(BaseTest):
|
||||
__test__ = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.test_coin_from = Coins.LTC
|
||||
cls.start_ltc_nodes = True
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info('Finalising LTC Test')
|
||||
super(Test, cls).tearDownClass()
|
||||
|
||||
def getBalance(self, js_wallets):
|
||||
return float(js_wallets[str(int(self.test_coin_from))]['balance']) + float(js_wallets[str(int(self.test_coin_from))]['unconfirmed'])
|
||||
|
||||
def getXmrBalance(self, js_wallets):
|
||||
return float(js_wallets[str(int(Coins.XMR))]['unconfirmed']) + float(js_wallets[str(int(Coins.XMR))]['balance'])
|
||||
|
||||
def test_01_full_swap(self):
|
||||
logging.info('---------- Test {} to XMR'.format(str(self.test_coin_from)))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_before = self.getBalance(js_0)
|
||||
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
node1_from_before = self.getBalance(js_1)
|
||||
|
||||
js_0_xmr = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/xmr').read())
|
||||
js_1_xmr = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/xmr').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[0].listOffers(filters={'offer_id': offer_id})
|
||||
offer = offers[0]
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
node1_from_after = self.getBalance(js_1)
|
||||
assert(node1_from_after > node1_from_before + (amount_from - 0.05))
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_after = self.getBalance(js_0)
|
||||
# TODO: Discard block rewards
|
||||
# assert(node0_from_after < node0_from_before - amount_from)
|
||||
|
||||
js_0_xmr_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/xmr').read())
|
||||
js_1_xmr_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/xmr').read())
|
||||
|
||||
scale_from = 8
|
||||
amount_to = int((amt_swap * rate_swap) // (10 ** scale_from))
|
||||
amount_to_float = float(format_amount(amount_to, 12))
|
||||
node1_xmr_after = float(js_1_xmr_after['unconfirmed']) + float(js_1_xmr_after['balance'])
|
||||
node1_xmr_before = float(js_1_xmr['unconfirmed']) + float(js_1_xmr['balance'])
|
||||
assert(node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
|
||||
|
||||
def test_02_leader_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(str(self.test_coin_from)))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_before = self.getBalance(js_w0_before)
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_from_after = self.getBalance(js_w0_after)
|
||||
|
||||
# TODO: Discard block rewards
|
||||
# assert(node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
def test_03_follower_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(str(self.test_coin_from)))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True)
|
||||
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
node1_from_before = self.getBalance(js_w1_before)
|
||||
node1_from_after = self.getBalance(js_w1_after)
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
# TODO: Discard block rewards
|
||||
# assert(node1_from_after - node1_from_before > (amount_from - 0.02))
|
||||
|
||||
wait_for_none_active(test_delay_event, 1800)
|
||||
wait_for_none_active(test_delay_event, 1801)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(str(self.test_coin_from)))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=18)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
node0_from_before = self.getBalance(js_w0_before)
|
||||
node0_from_after = self.getBalance(js_w0_after)
|
||||
|
||||
# TODO: Discard block rewards
|
||||
# assert(node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
node1_xmr_before = self.getXmrBalance(js_w1_before)
|
||||
node1_xmr_after = self.getXmrBalance(js_w1_after)
|
||||
assert(node1_xmr_before - node1_xmr_after < 0.02)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
235
tests/basicswap/test_partblind_xmr.py
Normal file
235
tests/basicswap/test_partblind_xmr.py
Normal file
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import unittest
|
||||
from urllib.request import urlopen
|
||||
|
||||
from basicswap.basicswap import (
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
DebugTypes,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
SEQUENCE_LOCK_BLOCKS,
|
||||
)
|
||||
from basicswap.util import (
|
||||
make_int,
|
||||
format_amount,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
wait_for_bid,
|
||||
wait_for_offer,
|
||||
wait_for_none_active,
|
||||
wait_for_balance,
|
||||
post_json_req,
|
||||
)
|
||||
|
||||
from .test_xmr import BaseTest, test_delay_event
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Test(BaseTest):
|
||||
__test__ = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
node0_blind_before = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
|
||||
post_json = {
|
||||
'value': 100,
|
||||
'address': js_0['stealth_address'],
|
||||
'subfee': False,
|
||||
'type_to': 'blind',
|
||||
}
|
||||
json_rv = json.loads(post_json_req('http://127.0.0.1:1800/json/wallets/part/withdraw', post_json))
|
||||
assert(len(json_rv['txid']) == 64)
|
||||
|
||||
logging.info('Waiting for blind balance')
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1800/json/wallets/part', 'blind_balance', 100.0 + node0_blind_before)
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
node0_blind_before = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
|
||||
def getBalance(self, js_wallets):
|
||||
return float(js_wallets[str(int(Coins.PART))]['blind_balance']) + float(js_wallets[str(int(Coins.PART))]['blind_unconfirmed'])
|
||||
|
||||
def getXmrBalance(self, js_wallets):
|
||||
return float(js_wallets[str(int(Coins.XMR))]['unconfirmed']) + float(js_wallets[str(int(Coins.XMR))]['balance'])
|
||||
|
||||
def test_01_part_xmr(self):
|
||||
logging.info('---------- Test PARTct to XMR')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
assert(float(js_0['blind_balance']) > 10.0)
|
||||
node0_blind_before = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
node1_blind_before = js_1['blind_balance'] + js_1['blind_unconfirmed']
|
||||
|
||||
js_0_xmr = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/xmr').read())
|
||||
js_1_xmr = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/xmr').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(Coins.PART_BLIND, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[0].listOffers(filters={'offer_id': offer_id})
|
||||
offer = offers[0]
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
node1_blind_after = js_1['blind_balance'] + js_1['blind_unconfirmed']
|
||||
assert(node1_blind_after > node1_blind_before + (amount_from - 0.05))
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
node0_blind_after = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
assert(node0_blind_after < node0_blind_before - amount_from)
|
||||
|
||||
js_0_xmr_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/xmr').read())
|
||||
js_1_xmr_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/xmr').read())
|
||||
|
||||
scale_from = 8
|
||||
amount_to = int((amt_swap * rate_swap) // (10 ** scale_from))
|
||||
amount_to_float = float(format_amount(amount_to, 12))
|
||||
node1_xmr_after = float(js_1_xmr_after['unconfirmed']) + float(js_1_xmr_after['balance'])
|
||||
node1_xmr_before = float(js_1_xmr['unconfirmed']) + float(js_1_xmr['balance'])
|
||||
assert(node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
|
||||
|
||||
def test_02_leader_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test PARTct to XMR leader recovers coin a lock tx')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_blind_before = self.getBalance(js_w0_before)
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
Coins.PART_BLIND, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
node0_blind_after = self.getBalance(js_w0_after)
|
||||
assert(node0_blind_before - node0_blind_after < 0.02)
|
||||
|
||||
def test_03_follower_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test PARTct to XMR follower recovers coin a lock tx')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
Coins.PART_BLIND, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True)
|
||||
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
node1_blind_before = self.getBalance(js_w1_before)
|
||||
node1_blind_after = self.getBalance(js_w1_after)
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
assert(node1_blind_after - node1_blind_before > (amount_from - 0.02))
|
||||
|
||||
wait_for_none_active(test_delay_event, 1800)
|
||||
wait_for_none_active(test_delay_event, 1801)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx(self):
|
||||
logging.info('---------- Test PARTct to XMR follower recovers coin b lock tx')
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
Coins.PART_BLIND, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=18)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
|
||||
node0_blind_before = self.getBalance(js_w0_before)
|
||||
node0_blind_after = self.getBalance(js_w0_after)
|
||||
assert(node0_blind_before - node0_blind_after < 0.02)
|
||||
|
||||
node1_xmr_before = self.getXmrBalance(js_w1_before)
|
||||
node1_xmr_after = self.getXmrBalance(js_w1_after)
|
||||
assert(node1_xmr_before - node1_xmr_after < 0.02)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -70,6 +70,8 @@ from tests.basicswap.common import (
|
|||
BASE_ZMQ_PORT,
|
||||
BTC_BASE_PORT,
|
||||
BTC_BASE_RPC_PORT,
|
||||
LTC_BASE_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
PREFIX_SECRET_KEY_REGTEST,
|
||||
)
|
||||
from bin.basicswap_run import startDaemon, startXmrDaemon
|
||||
|
@ -80,6 +82,7 @@ logger = logging.getLogger()
|
|||
NUM_NODES = 3
|
||||
NUM_XMR_NODES = 3
|
||||
NUM_BTC_NODES = 3
|
||||
NUM_LTC_NODES = 3
|
||||
TEST_DIR = cfg.TEST_DATADIRS
|
||||
|
||||
XMR_BASE_P2P_PORT = 17792
|
||||
|
@ -139,7 +142,7 @@ def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]):
|
|||
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
|
||||
|
||||
|
||||
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey):
|
||||
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_ltc=False):
|
||||
basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
|
||||
if not os.path.exists(basicswap_dir):
|
||||
os.makedirs(basicswap_dir)
|
||||
|
@ -198,6 +201,18 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey):
|
|||
'max_delay_retry': 10
|
||||
}
|
||||
|
||||
if with_ltc:
|
||||
settings['chainclients']['litecoin'] = {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': LTC_BASE_RPC_PORT + node_id,
|
||||
'rpcuser': 'test' + str(node_id),
|
||||
'rpcpassword': 'test_pass' + str(node_id),
|
||||
'datadir': os.path.join(datadir, 'ltc_' + str(node_id)),
|
||||
'bindir': cfg.LITECOIN_BINDIR,
|
||||
'use_segwit': True,
|
||||
}
|
||||
|
||||
with open(settings_path, 'w') as fp:
|
||||
json.dump(settings, fp, indent=4)
|
||||
|
||||
|
@ -206,6 +221,10 @@ def btcRpc(cmd, node_id=0):
|
|||
return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(TEST_DIR, 'btc_' + str(node_id)), 'regtest', cmd, cfg.BITCOIN_CLI)
|
||||
|
||||
|
||||
def ltcRpc(cmd, node_id=0):
|
||||
return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(TEST_DIR, 'ltc_' + str(node_id)), 'regtest', cmd, cfg.LITECOIN_CLI)
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
logging.info('signal {} detected.'.format(sig))
|
||||
test_delay_event.set()
|
||||
|
@ -249,6 +268,8 @@ def run_coins_loop(cls):
|
|||
try:
|
||||
if cls.btc_addr is not None:
|
||||
btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr))
|
||||
if cls.ltc_addr is not None:
|
||||
ltcRpc('generatetoaddress 1 {}'.format(cls.ltc_addr))
|
||||
if cls.xmr_addr is not None:
|
||||
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
|
||||
except Exception as e:
|
||||
|
@ -263,12 +284,13 @@ def run_loop(cls):
|
|||
test_delay_event.wait(1.0)
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
class BaseTest(unittest.TestCase):
|
||||
__test__ = False
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
if not hasattr(cls, 'start_ltc_nodes'):
|
||||
cls.start_ltc_nodes = False
|
||||
random.seed(time.time())
|
||||
|
||||
cls.update_thread = None
|
||||
|
@ -277,11 +299,13 @@ class Test(unittest.TestCase):
|
|||
cls.swap_clients = []
|
||||
cls.part_daemons = []
|
||||
cls.btc_daemons = []
|
||||
cls.ltc_daemons = []
|
||||
cls.xmr_daemons = []
|
||||
cls.xmr_wallet_auth = []
|
||||
|
||||
cls.xmr_addr = None
|
||||
cls.btc_addr = None
|
||||
cls.ltc_addr = None
|
||||
|
||||
logger.propagate = False
|
||||
logger.handlers = []
|
||||
|
@ -336,6 +360,17 @@ class Test(unittest.TestCase):
|
|||
|
||||
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT))
|
||||
|
||||
if cls.start_ltc_nodes:
|
||||
for i in range(NUM_LTC_NODES):
|
||||
data_dir = prepareDataDir(TEST_DIR, i, 'litecoin.conf', 'ltc_', base_p2p_port=LTC_BASE_PORT, base_rpc_port=LTC_BASE_RPC_PORT)
|
||||
if os.path.exists(os.path.join(cfg.LITECOIN_BINDIR, 'litecoin-wallet')):
|
||||
callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet')
|
||||
|
||||
cls.ltc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'ltc_' + str(i)), cfg.LITECOIN_BINDIR, cfg.LITECOIND))
|
||||
logging.info('Started %s %d', cfg.LITECOIND, cls.part_daemons[-1].pid)
|
||||
|
||||
waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT))
|
||||
|
||||
for i in range(NUM_XMR_NODES):
|
||||
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
|
||||
|
||||
|
@ -361,7 +396,7 @@ class Test(unittest.TestCase):
|
|||
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey)
|
||||
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, cls.start_ltc_nodes)
|
||||
basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
with open(settings_path) as fs:
|
||||
|
@ -370,6 +405,10 @@ class Test(unittest.TestCase):
|
|||
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
|
||||
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid)
|
||||
sc.setDaemonPID(Coins.PART, cls.part_daemons[i].pid)
|
||||
|
||||
if cls.start_ltc_nodes:
|
||||
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid)
|
||||
|
||||
sc.start()
|
||||
# Set XMR main wallet address
|
||||
xmr_ci = sc.ci(Coins.XMR)
|
||||
|
@ -389,6 +428,12 @@ class Test(unittest.TestCase):
|
|||
|
||||
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT))
|
||||
|
||||
if cls.start_ltc_nodes:
|
||||
cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT)
|
||||
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
|
||||
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
|
||||
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
|
||||
|
||||
num_blocks = 100
|
||||
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
|
||||
logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr)
|
||||
|
@ -441,12 +486,17 @@ class Test(unittest.TestCase):
|
|||
stopDaemons(cls.xmr_daemons)
|
||||
stopDaemons(cls.part_daemons)
|
||||
stopDaemons(cls.btc_daemons)
|
||||
stopDaemons(cls.ltc_daemons)
|
||||
|
||||
super(Test, cls).tearDownClass()
|
||||
super(BaseTest, cls).tearDownClass()
|
||||
|
||||
def callxmrnodewallet(self, node_id, method, params=None):
|
||||
return callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + node_id, self.xmr_wallet_auth[node_id], method, params)
|
||||
|
||||
|
||||
class Test(BaseTest):
|
||||
__test__ = True
|
||||
|
||||
def test_01_part_xmr(self):
|
||||
logging.info('---------- Test PART to XMR')
|
||||
swap_clients = self.swap_clients
|
||||
|
@ -601,8 +651,6 @@ class Test(unittest.TestCase):
|
|||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
print('[rm] js_w0_before', json.dumps(js_w0_before))
|
||||
print('[rm] js_w0_after', json.dumps(js_w0_after))
|
||||
|
||||
def test_03_follower_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test PART to XMR follower recovers coin a lock tx')
|
||||
|
@ -740,9 +788,6 @@ class Test(unittest.TestCase):
|
|||
|
||||
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())
|
||||
js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read())
|
||||
logging.info('[rm] js_w0_after {}'.format(json.dumps(js_w0_after, indent=4)))
|
||||
logging.info('[rm] js_w1_after {}'.format(json.dumps(js_w1_after, indent=4)))
|
||||
|
||||
assert(make_int(js_w1_after['2']['balance'], scale=8, r=1) - (make_int(js_w1_before['2']['balance'], scale=8, r=1) + amt_1) < 1000)
|
||||
|
||||
def test_07_revoke_offer(self):
|
||||
|
@ -875,10 +920,58 @@ class Test(unittest.TestCase):
|
|||
assert(js_1['anon_balance'] < node1_anon_before - amount_to)
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
assert(js_0['anon_balance'] + js_0['anon_pending'] > node0_anon_before + (amount_to - 0.1))
|
||||
assert(js_0['anon_balance'] + js_0['anon_pending'] > node0_anon_before + (amount_to - 0.05))
|
||||
|
||||
def test_12_particl_blind(self):
|
||||
return # TODO
|
||||
logging.info('---------- Test Particl blind transactions')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
node0_blind_before = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/part', 'balance', 200.0)
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
assert(float(js_1['balance']) > 200.0)
|
||||
node1_blind_before = js_1['blind_balance'] + js_1['blind_unconfirmed']
|
||||
|
||||
post_json = {
|
||||
'value': 100,
|
||||
'address': js_0['stealth_address'],
|
||||
'subfee': False,
|
||||
'type_to': 'blind',
|
||||
}
|
||||
json_rv = json.loads(post_json_req('http://127.0.0.1:1800/json/wallets/part/withdraw', post_json))
|
||||
assert(len(json_rv['txid']) == 64)
|
||||
|
||||
logging.info('Waiting for blind balance')
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1800/json/wallets/part', 'blind_balance', 100.0 + node0_blind_before)
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
node0_blind_before = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(2.0, 20.0), scale=8, r=1)
|
||||
offer_id = swap_clients[0].postOffer(Coins.PART_BLIND, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[0].listOffers(filters={'offer_id': offer_id})
|
||||
offer = offers[0]
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
node1_blind_after = js_1['blind_balance'] + js_1['blind_unconfirmed']
|
||||
assert(node1_blind_after > node1_blind_before + (amount_from - 0.05))
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
node0_blind_after = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
||||
assert(node0_blind_after < node0_blind_before - amount_from)
|
||||
|
||||
def test_98_withdraw_all(self):
|
||||
logging.info('---------- Test XMR withdrawal all')
|
||||
|
|
Loading…
Reference in a new issue