mirror of
https://github.com/basicswap/basicswap.git
synced 2025-03-25 00:28:53 +00:00
WIP continue implementing the BCH swap interface for XMR swap and atomic swap protocols
This commit is contained in:
parent
a4b411f1fd
commit
58b42c0d9a
9 changed files with 428 additions and 122 deletions
|
@ -251,7 +251,6 @@ class BasicSwap(BaseApp):
|
||||||
protocolInterfaces = {
|
protocolInterfaces = {
|
||||||
SwapTypes.SELLER_FIRST: atomic_swap_1.AtomicSwapInterface(),
|
SwapTypes.SELLER_FIRST: atomic_swap_1.AtomicSwapInterface(),
|
||||||
SwapTypes.XMR_SWAP: xmr_swap_1.XmrSwapInterface(),
|
SwapTypes.XMR_SWAP: xmr_swap_1.XmrSwapInterface(),
|
||||||
SwapTypes.XMR_BCH_SWAP: xmr_swap_1.XmrBchSwapInterface(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap', transient_instance=False):
|
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap', transient_instance=False):
|
||||||
|
@ -675,6 +674,9 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
return self.coin_clients[use_coinid][interface_ind]
|
return self.coin_clients[use_coinid][interface_ind]
|
||||||
|
|
||||||
|
def isBchXmrSwap(self, offer: Offer):
|
||||||
|
return (offer['coin_from'] == Coins.BCH or offer['coin_to'] == Coins.BCH) and offer.swap_type == SwapTypes.XMR_SWAP
|
||||||
|
|
||||||
def pi(self, protocol_ind):
|
def pi(self, protocol_ind):
|
||||||
if protocol_ind not in self.protocolInterfaces:
|
if protocol_ind not in self.protocolInterfaces:
|
||||||
raise ValueError('Unknown protocol_ind {}'.format(int(protocol_ind)))
|
raise ValueError('Unknown protocol_ind {}'.format(int(protocol_ind)))
|
||||||
|
@ -2976,7 +2978,32 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
# MSG2F
|
# MSG2F
|
||||||
pi = self.pi(SwapTypes.XMR_SWAP)
|
pi = self.pi(SwapTypes.XMR_SWAP)
|
||||||
xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript(ci_from, xmr_swap.pkal, xmr_swap.pkaf)
|
|
||||||
|
refundExtraArgs = dict()
|
||||||
|
lockExtraArgs = dict()
|
||||||
|
if self.isBchXmrSwap(offer):
|
||||||
|
pkh_refund_to = ci_from.decodeAddress(self.getCachedAddressForCoin(coin_from))
|
||||||
|
pkh_dest = xmr_swap.dest_af
|
||||||
|
|
||||||
|
# refund script
|
||||||
|
refundExtraArgs['mining_fee'] = 1000
|
||||||
|
refundExtraArgs['out_1'] = ci_from.getScriptForPubkeyHash(pkh_refund_to)
|
||||||
|
refundExtraArgs['out_2'] = ci_from.getScriptForPubkeyHash(pkh_dest)
|
||||||
|
refundExtraArgs['public_key'] = xmr_swap.pkaf
|
||||||
|
refundExtraArgs['timelock'] = xmr_offer.lock_time_2
|
||||||
|
|
||||||
|
refund_lock_tx_script = pi.genScriptLockTxScript(ci_from, xmr_swap.pkal, xmr_swap.pkaf, refundExtraArgs)
|
||||||
|
# will make use of this in `createSCLockRefundTx`
|
||||||
|
refundExtraArgs['refund_lock_tx_script'] = refund_lock_tx_script
|
||||||
|
|
||||||
|
# lock script
|
||||||
|
lockExtraArgs['mining_fee'] = 1000
|
||||||
|
lockExtraArgs['out_1'] = ci_from.getScriptForPubkeyHash(pkh_refund_to)
|
||||||
|
lockExtraArgs['out_2'] = ci_from.scriptToP2SH32LockingBytecode(refund_lock_tx_script)
|
||||||
|
lockExtraArgs['public_key'] = xmr_swap.pkal
|
||||||
|
lockExtraArgs['timelock'] = xmr_offer.lock_time_1
|
||||||
|
|
||||||
|
xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript(ci_from, xmr_swap.pkal, xmr_swap.pkaf, lockExtraArgs)
|
||||||
prefunded_tx = self.getPreFundedTx(Concepts.OFFER, bid.offer_id, TxTypes.ITX_PRE_FUNDED, session=use_session)
|
prefunded_tx = self.getPreFundedTx(Concepts.OFFER, bid.offer_id, TxTypes.ITX_PRE_FUNDED, session=use_session)
|
||||||
if prefunded_tx:
|
if prefunded_tx:
|
||||||
xmr_swap.a_lock_tx = pi.promoteMockTx(ci_from, prefunded_tx, xmr_swap.a_lock_tx_script)
|
xmr_swap.a_lock_tx = pi.promoteMockTx(ci_from, prefunded_tx, xmr_swap.a_lock_tx_script)
|
||||||
|
@ -2994,7 +3021,7 @@ class BasicSwap(BaseApp):
|
||||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||||
xmr_swap.pkal, xmr_swap.pkaf,
|
xmr_swap.pkal, xmr_swap.pkaf,
|
||||||
xmr_offer.lock_time_1, xmr_offer.lock_time_2,
|
xmr_offer.lock_time_1, xmr_offer.lock_time_2,
|
||||||
a_fee_rate, xmr_swap.vkbv
|
a_fee_rate, xmr_swap.vkbv, refundExtraArgs
|
||||||
)
|
)
|
||||||
xmr_swap.a_lock_refund_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
xmr_swap.a_lock_refund_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
||||||
|
|
||||||
|
@ -3003,7 +3030,7 @@ class BasicSwap(BaseApp):
|
||||||
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)
|
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')
|
ensure(v, 'Invalid coin A lock refund tx leader sig')
|
||||||
|
|
||||||
pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from))
|
pkh_refund_to = ci_from.decodeAddress(self.getCachedAddressForCoin(coin_from))
|
||||||
xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx(
|
xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx(
|
||||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||||
pkh_refund_to,
|
pkh_refund_to,
|
||||||
|
@ -3894,6 +3921,7 @@ class BasicSwap(BaseApp):
|
||||||
self.createActionInSession(delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
self.createActionInSession(delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||||
session.commit()
|
session.commit()
|
||||||
elif state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
|
elif state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
|
||||||
|
print(3, bid.xmr_a_lock_tx)
|
||||||
if bid.xmr_a_lock_tx is None:
|
if bid.xmr_a_lock_tx is None:
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
@ -4847,10 +4875,11 @@ class BasicSwap(BaseApp):
|
||||||
raise ValueError('TODO')
|
raise ValueError('TODO')
|
||||||
elif offer_data.swap_type == SwapTypes.XMR_SWAP:
|
elif offer_data.swap_type == SwapTypes.XMR_SWAP:
|
||||||
ensure(offer_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Invalid protocol version')
|
ensure(offer_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Invalid protocol version')
|
||||||
if reverse_bid:
|
if Coins.BCH not in (coin_from, coin_to):
|
||||||
ensure(ci_to.has_segwit(), 'Coin-to must support segwit for reverse bid offers')
|
if reverse_bid:
|
||||||
else:
|
ensure(ci_to.has_segwit(), 'Coin-to must support segwit for reverse bid offers')
|
||||||
ensure(ci_from.has_segwit(), 'Coin-from must support segwit')
|
else:
|
||||||
|
ensure(ci_from.has_segwit(), 'Coin-from must support segwit')
|
||||||
ensure(len(offer_data.proof_address) == 0, 'Unexpected data')
|
ensure(len(offer_data.proof_address) == 0, 'Unexpected data')
|
||||||
ensure(len(offer_data.proof_signature) == 0, 'Unexpected data')
|
ensure(len(offer_data.proof_signature) == 0, 'Unexpected data')
|
||||||
ensure(len(offer_data.pkhash_seller) == 0, 'Unexpected data')
|
ensure(len(offer_data.pkhash_seller) == 0, 'Unexpected data')
|
||||||
|
@ -5695,6 +5724,7 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
if lock_tx_sent is False:
|
if lock_tx_sent is False:
|
||||||
lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
|
lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
|
||||||
|
print(1, lock_tx_signed)
|
||||||
txid_hex = ci_from.publishTx(lock_tx_signed)
|
txid_hex = ci_from.publishTx(lock_tx_signed)
|
||||||
|
|
||||||
vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
|
vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
|
||||||
|
@ -5764,6 +5794,7 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, b_fee_rate, unlock_time=unlock_time)
|
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, b_fee_rate, unlock_time=unlock_time)
|
||||||
|
print(2, b_lock_tx_id)
|
||||||
if bid.debug_ind == DebugTypes.B_LOCK_TX_MISSED_SEND:
|
if bid.debug_ind == DebugTypes.B_LOCK_TX_MISSED_SEND:
|
||||||
self.log.debug('Adaptor-sig bid %s: Debug %d - Losing xmr lock tx %s.', bid_id.hex(), bid.debug_ind, b_lock_tx_id.hex())
|
self.log.debug('Adaptor-sig bid %s: Debug %d - Losing xmr lock tx %s.', bid_id.hex(), bid.debug_ind, b_lock_tx_id.hex())
|
||||||
self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
|
self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
|
||||||
|
|
|
@ -49,8 +49,8 @@ LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
|
||||||
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0')
|
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0')
|
||||||
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
||||||
|
|
||||||
BITCOINCASH_VERSION = os.getenv('BITCOIN_VERSION', '27.1.0')
|
BITCOINCASH_VERSION = os.getenv('BITCOINCASH_VERSION', '27.1.0')
|
||||||
BITCOINCASH_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
BITCOINCASH_VERSION_TAG = os.getenv('BITCOINCASH_VERSION_TAG', '')
|
||||||
|
|
||||||
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.4')
|
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.4')
|
||||||
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
||||||
|
|
|
@ -440,6 +440,9 @@ chainparams = {
|
||||||
'message_magic': 'Bitcoin Signed Message:\n',
|
'message_magic': 'Bitcoin Signed Message:\n',
|
||||||
'blocks_target': 60 * 2,
|
'blocks_target': 60 * 2,
|
||||||
'decimal_places': 8,
|
'decimal_places': 8,
|
||||||
|
'has_cltv': True,
|
||||||
|
'has_csv': True,
|
||||||
|
'has_segwit': False,
|
||||||
'mainnet': {
|
'mainnet': {
|
||||||
'rpcport': 8332,
|
'rpcport': 8332,
|
||||||
'pubkey_address': 0,
|
'pubkey_address': 0,
|
||||||
|
|
|
@ -1,22 +1,62 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from basicswap.contrib.test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut
|
from basicswap.contrib.test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut
|
||||||
from basicswap.util import ensure, i2h
|
from basicswap.util import b2h, ensure, i2h
|
||||||
from .btc import BTCInterface, findOutput
|
from .btc import BTCInterface, findOutput
|
||||||
from basicswap.rpc import make_rpc_func
|
from basicswap.rpc import make_rpc_func
|
||||||
from basicswap.chainparams import Coins, chainparams
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.interface.contrib.bch_test_framework.cashaddress import Address
|
from basicswap.interface.contrib.bch_test_framework.cashaddress import Address
|
||||||
from basicswap.util.crypto import hash160, sha256
|
from basicswap.util.crypto import hash160, sha256
|
||||||
from basicswap.interface.contrib.bch_test_framework.script import OP_EQUAL, OP_EQUALVERIFY, OP_HASH256, OP_DUP, OP_HASH160, OP_CHECKSIG
|
from basicswap.interface.contrib.bch_test_framework.script import (
|
||||||
|
OP_TXINPUTCOUNT,
|
||||||
|
OP_1,
|
||||||
|
OP_NUMEQUALVERIFY,
|
||||||
|
OP_TXOUTPUTCOUNT,
|
||||||
|
OP_0,
|
||||||
|
OP_UTXOVALUE,
|
||||||
|
OP_OUTPUTVALUE,
|
||||||
|
OP_SUB,
|
||||||
|
OP_UTXOTOKENCATEGORY,
|
||||||
|
OP_OUTPUTTOKENCATEGORY,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_UTXOTOKENCOMMITMENT,
|
||||||
|
OP_OUTPUTTOKENCOMMITMENT,
|
||||||
|
OP_UTXOTOKENAMOUNT,
|
||||||
|
OP_OUTPUTTOKENAMOUNT,
|
||||||
|
OP_INPUTSEQUENCENUMBER,
|
||||||
|
OP_NOTIF,
|
||||||
|
OP_OUTPUTBYTECODE,
|
||||||
|
OP_OVER,
|
||||||
|
OP_CHECKDATASIG,
|
||||||
|
OP_ELSE,
|
||||||
|
OP_CHECKSEQUENCEVERIFY,
|
||||||
|
OP_DROP,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_ENDIF,
|
||||||
|
OP_HASH160,
|
||||||
|
OP_DUP,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_HASH256,
|
||||||
|
)
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
CScript, CScriptOp,
|
CScript, CScriptOp,
|
||||||
)
|
)
|
||||||
|
from coincurve.keys import (
|
||||||
|
PrivateKey,
|
||||||
|
PublicKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
def findOutput(tx, script_pk: bytes):
|
||||||
|
for i in range(len(tx.vout)):
|
||||||
|
if tx.vout[i].scriptPubKey == script_pk:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
class BCHInterface(BTCInterface):
|
class BCHInterface(BTCInterface):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -25,11 +65,184 @@ class BCHInterface(BTCInterface):
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(BCHInterface, self).__init__(coin_settings, network, swap_client)
|
super(BCHInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
|
# No multiwallet support
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
|
|
||||||
|
def getExchangeName(self, exchange_name):
|
||||||
|
return 'bch'
|
||||||
|
|
||||||
|
def getNewAddress(self, use_segwit: bool = False, label: str = 'swap_receive') -> str:
|
||||||
|
args = [label]
|
||||||
|
return self.rpc_wallet('getnewaddress', args)
|
||||||
|
|
||||||
|
# returns pkh
|
||||||
def decodeAddress(self, address: str) -> bytes:
|
def decodeAddress(self, address: str) -> bytes:
|
||||||
return bytes(Address.from_string(address).payload)
|
return bytes(Address.from_string(address).payload)
|
||||||
|
|
||||||
|
def encodeSegwitAddress(self, script):
|
||||||
|
raise ValueError('TODO')
|
||||||
|
|
||||||
|
def decodeSegwitAddress(self, addr):
|
||||||
|
raise ValueError('TODO')
|
||||||
|
|
||||||
|
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||||
|
lock_tx_dest = self.getScriptDest(lock_script)
|
||||||
|
address = self.encodeScriptDest(lock_tx_dest)
|
||||||
|
|
||||||
|
if not self.isAddressMine(address, or_watch_only=True):
|
||||||
|
# Expects P2WSH nested in BIP16_P2SH
|
||||||
|
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
||||||
|
addr_info = self.rpc('validateaddress', [address])
|
||||||
|
|
||||||
|
return address
|
||||||
|
|
||||||
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
|
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
|
|
||||||
|
options = {
|
||||||
|
'lockUnspents': lock_unspents,
|
||||||
|
# 'conf_target': self._conf_target,
|
||||||
|
}
|
||||||
|
if sub_fee:
|
||||||
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
|
return self.rpc_wallet('fundrawtransaction', [txn, options])['hex']
|
||||||
|
|
||||||
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
|
# Return P2PKH
|
||||||
|
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
|
|
||||||
|
# def getScriptDest(self, script: bytearray) -> bytearray:
|
||||||
|
# # P2SH
|
||||||
|
|
||||||
|
# script_hash = hash160(script)
|
||||||
|
# assert len(script_hash) == 20
|
||||||
|
|
||||||
|
# return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def encodeScriptDest(self, script_dest: bytes) -> str:
|
||||||
|
# Extract hash from script
|
||||||
|
script_hash = script_dest[2:-1]
|
||||||
|
return self.sh_to_address(script_hash)
|
||||||
|
|
||||||
|
def sh_to_address(self, sh: bytes) -> str:
|
||||||
|
assert (len(sh) == 20)
|
||||||
|
network = self._network.upper()
|
||||||
|
address = Address("P2SH20" if network == "MAINNET" else "P2SH20-"+network, sh)
|
||||||
|
return address.cash_address()
|
||||||
|
|
||||||
|
def getDestForScriptHash(self, script_hash):
|
||||||
|
assert len(script_hash) == 20
|
||||||
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def withdrawCoin(self, value: float, addr_to: str, subfee: bool):
|
||||||
|
params = [addr_to, value, '', '', subfee, True, True]
|
||||||
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
|
def getSpendableBalance(self) -> int:
|
||||||
|
return self.make_int(self.rpc('getwalletinfo')['unconfirmed_balance'])
|
||||||
|
|
||||||
|
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||||
|
add_bytes = 107
|
||||||
|
size = len(tx.serialize_with_witness()) + add_bytes
|
||||||
|
pay_fee = round(fee_rate * size / 1000)
|
||||||
|
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||||
|
return pay_fee
|
||||||
|
|
||||||
|
def findTxnByHash(self, txid_hex: str):
|
||||||
|
# Only works for wallet txns
|
||||||
|
try:
|
||||||
|
rv = self.rpc('gettransaction', [txid_hex])
|
||||||
|
except Exception as ex:
|
||||||
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
|
return None
|
||||||
|
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
||||||
|
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
||||||
|
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
||||||
|
print("bch genScriptLockTxScript")
|
||||||
|
mining_fee: int = kwargs['mining_fee']
|
||||||
|
out_1: bytes = kwargs['out_1']
|
||||||
|
out_2: bytes = kwargs['out_2']
|
||||||
|
public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal
|
||||||
|
timelock: int = kwargs['timelock']
|
||||||
|
|
||||||
|
return CScript([
|
||||||
|
# // v4.1.0-CashTokens-Optimized
|
||||||
|
# // Based on swaplock.cash v4.1.0-CashTokens
|
||||||
|
#
|
||||||
|
# // Alice has XMR, wants BCH and/or CashTokens.
|
||||||
|
# // Bob has BCH and/or CashTokens, wants XMR.
|
||||||
|
#
|
||||||
|
# // Verify 1-in-1-out TX form
|
||||||
|
CScriptOp(OP_TXINPUTCOUNT),
|
||||||
|
CScriptOp(OP_1), CScriptOp(OP_NUMEQUALVERIFY),
|
||||||
|
CScriptOp(OP_TXOUTPUTCOUNT),
|
||||||
|
CScriptOp(OP_1), CScriptOp(OP_NUMEQUALVERIFY),
|
||||||
|
|
||||||
|
# // int miningFee
|
||||||
|
mining_fee,
|
||||||
|
# // Verify pre-agreed mining fee and that the rest of BCH is forwarded
|
||||||
|
# // to the output.
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_UTXOVALUE),
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_OUTPUTVALUE),
|
||||||
|
CScriptOp(OP_SUB), CScriptOp(OP_NUMEQUALVERIFY),
|
||||||
|
|
||||||
|
# # // Verify that any CashTokens are forwarded to the output.
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENCATEGORY),
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENCATEGORY),
|
||||||
|
CScriptOp(OP_EQUALVERIFY),
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENCOMMITMENT),
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENCOMMITMENT),
|
||||||
|
CScriptOp(OP_EQUALVERIFY),
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENAMOUNT),
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENAMOUNT),
|
||||||
|
CScriptOp(OP_NUMEQUALVERIFY),
|
||||||
|
|
||||||
|
# // If sequence is not used then it is a regular swap TX.
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_INPUTSEQUENCENUMBER),
|
||||||
|
CScriptOp(OP_NOTIF),
|
||||||
|
# // bytes aliceOutput
|
||||||
|
out_1,
|
||||||
|
# // Verify that the BCH and/or CashTokens are forwarded to Alice's
|
||||||
|
# // output.
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_OUTPUTBYTECODE),
|
||||||
|
CScriptOp(OP_OVER), CScriptOp(OP_EQUALVERIFY),
|
||||||
|
|
||||||
|
# // pubkey bobPubkeyVES
|
||||||
|
public_key,
|
||||||
|
# // Require Alice to decrypt and publish Bob's VES signature.
|
||||||
|
# // The "message" signed is simply a sha256 hash of Alice's output
|
||||||
|
# // locking bytecode.
|
||||||
|
# // By decrypting Bob's VES and publishing it, Alice reveals her
|
||||||
|
# // XMR key share to Bob.
|
||||||
|
CScriptOp(OP_CHECKDATASIG),
|
||||||
|
|
||||||
|
# // If a TX using this path is mined then Alice gets her BCH.
|
||||||
|
# // Bob uses the revealed XMR key share to collect his XMR.
|
||||||
|
|
||||||
|
# // Refund will become available when timelock expires, and it would
|
||||||
|
# // expire because Alice didn't collect on time, either of her own accord
|
||||||
|
# // or because Bob bailed out and witheld the encrypted signature.
|
||||||
|
CScriptOp(OP_ELSE),
|
||||||
|
# // int timelock_0
|
||||||
|
timelock,
|
||||||
|
# // Verify refund timelock.
|
||||||
|
CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
|
||||||
|
|
||||||
|
# // bytes refundLockingBytecode
|
||||||
|
out_2,
|
||||||
|
|
||||||
|
# // Verify that the BCH and/or CashTokens are forwarded to Refund
|
||||||
|
# // contract.
|
||||||
|
CScriptOp(OP_0), CScriptOp(OP_OUTPUTBYTECODE),
|
||||||
|
CScriptOp(OP_EQUAL),
|
||||||
|
|
||||||
|
# // BCH and/or CashTokens are simply forwarded to Refund contract.
|
||||||
|
CScriptOp(OP_ENDIF)
|
||||||
|
])
|
||||||
|
|
||||||
def pubkey_to_segwit_address(self, pk: bytes) -> str:
|
def pubkey_to_segwit_address(self, pk: bytes) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -41,10 +254,6 @@ class BCHInterface(BTCInterface):
|
||||||
address.prefix = prefix
|
address.prefix = prefix
|
||||||
return address.cash_address()
|
return address.cash_address()
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit: bool = False, label: str = 'swap_receive') -> str:
|
|
||||||
args = [label]
|
|
||||||
return self.rpc_wallet('getnewaddress', args)
|
|
||||||
|
|
||||||
def addressToLockingBytecode(self, address: str) -> bytes:
|
def addressToLockingBytecode(self, address: str) -> bytes:
|
||||||
return b'\x76\xa9\x14' + bytes(Address.from_string(address).payload) + b'\x88\xac'
|
return b'\x76\xa9\x14' + bytes(Address.from_string(address).payload) + b'\x88\xac'
|
||||||
|
|
||||||
|
@ -83,10 +292,11 @@ class BCHInterface(BTCInterface):
|
||||||
if ves is not None:
|
if ves is not None:
|
||||||
return CScript([ves, script])
|
return CScript([ves, script])
|
||||||
else:
|
else:
|
||||||
return CScript([script])
|
return CScript([0, script])
|
||||||
|
|
||||||
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, ves=None, fee_info={}):
|
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}, **kwargs):
|
||||||
# tx_fee_rate in this context is equal to `mining_fee` contract param
|
# tx_fee_rate in this context is equal to `mining_fee` contract param
|
||||||
|
ves = kwargs['ves'] if 'ves' in kwargs else None
|
||||||
tx_lock = self.loadTx(tx_lock_bytes)
|
tx_lock = self.loadTx(tx_lock_bytes)
|
||||||
output_script = self.getScriptDest(script_lock)
|
output_script = self.getScriptDest(script_lock)
|
||||||
locked_n = findOutput(tx_lock, output_script)
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
|
@ -111,9 +321,127 @@ class BCHInterface(BTCInterface):
|
||||||
fee_info['fee_paid'] = pay_fee
|
fee_info['fee_paid'] = pay_fee
|
||||||
fee_info['rate_used'] = tx_fee_rate
|
fee_info['rate_used'] = tx_fee_rate
|
||||||
fee_info['size'] = size
|
fee_info['size'] = size
|
||||||
|
# vsize is the same as size for BCH
|
||||||
|
fee_info['vsize'] = size
|
||||||
|
|
||||||
tx.rehash()
|
tx.rehash()
|
||||||
self._log.info('createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
|
self._log.info('createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
|
||||||
i2h(tx.sha256), tx_fee_rate, size, pay_fee)
|
i2h(tx.sha256), tx_fee_rate, size, pay_fee)
|
||||||
|
|
||||||
return tx.serialize_without_witness()
|
return tx.serialize_without_witness()
|
||||||
|
|
||||||
|
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None, **kwargs):
|
||||||
|
tx_lock = CTransaction()
|
||||||
|
tx_lock = self.loadTx(tx_lock_bytes)
|
||||||
|
|
||||||
|
output_script = self.getScriptDest(script_lock)
|
||||||
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock.rehash()
|
||||||
|
tx_lock_id_int = tx_lock.sha256
|
||||||
|
|
||||||
|
refund_script = kwargs['refund_lock_tx_script']
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
|
||||||
|
nSequence=kwargs['timelock'] if 'timelock' in kwargs else lock1_value,
|
||||||
|
scriptSig=self.getScriptScriptSig(script_lock, None)))
|
||||||
|
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
|
||||||
|
|
||||||
|
pay_fee = kwargs['mining_fee'] if 'mining_fee' in kwargs else tx_fee_rate
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
size = self.getTxSize(tx)
|
||||||
|
vsize = size
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx.serialize_without_witness(), refund_script, tx.vout[0].nValue
|
||||||
|
|
||||||
|
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None, **kwargs):
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# spending the refund contract back to leader requires their adaptor signature to be published, but at the moment of this function call it is too early to share it
|
||||||
|
# TODO: bettter handling of this case
|
||||||
|
# allow for template ves for transaction to be signed and verified between parties
|
||||||
|
ves = kwargs['ves'] if 'ves' in kwargs else bytes(70)
|
||||||
|
|
||||||
|
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||||
|
|
||||||
|
output_script = self.getScriptDest(script_lock_refund)
|
||||||
|
locked_n = findOutput(tx_lock_refund, output_script)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock_refund.rehash()
|
||||||
|
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
|
||||||
|
nSequence=0,
|
||||||
|
scriptSig=self.getScriptScriptSig(script_lock_refund, ves)))
|
||||||
|
|
||||||
|
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
|
||||||
|
|
||||||
|
pay_fee = tx_fee_rate
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
size = self.getTxSize(tx)
|
||||||
|
vsize = size
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx.serialize_without_witness()
|
||||||
|
|
||||||
|
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||||
|
# simply sign the entire tx data, as this is not a preimage signature
|
||||||
|
eck = PrivateKey(key_bytes)
|
||||||
|
return eck.sign(tx_bytes, hasher=None)
|
||||||
|
|
||||||
|
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||||
|
# simple ecdsa signature verification
|
||||||
|
pubkey = PublicKey(K)
|
||||||
|
return pubkey.verify(sig, tx_bytes, hasher=None)
|
||||||
|
|
||||||
|
def setTxSignature(self, tx_bytes: bytes, stack) -> bytes:
|
||||||
|
return tx_bytes
|
||||||
|
|
||||||
|
def verifySCLockTx(self, tx_bytes, script_out,
|
||||||
|
swap_value,
|
||||||
|
Kal, Kaf,
|
||||||
|
feerate,
|
||||||
|
check_lock_tx_inputs, vkbv=None):
|
||||||
|
# Verify:
|
||||||
|
#
|
||||||
|
|
||||||
|
# Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm
|
||||||
|
# However by checking early we can avoid wasting time processing unmineable txns
|
||||||
|
# Check fee is reasonable
|
||||||
|
|
||||||
|
tx = self.loadTx(tx_bytes)
|
||||||
|
txid = self.getTxid(tx)
|
||||||
|
self._log.info('Verifying lock tx: {}.'.format(b2h(txid)))
|
||||||
|
|
||||||
|
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||||
|
ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores
|
||||||
|
|
||||||
|
script_pk = self.getScriptDest(script_out)
|
||||||
|
locked_n = findOutput(tx, script_pk)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx.vout[locked_n].nValue
|
||||||
|
|
||||||
|
# Check value
|
||||||
|
ensure(locked_coin == swap_value, 'Bad locked value')
|
||||||
|
|
||||||
|
# TODO: better script matching, see interfaces/btc.py
|
||||||
|
|
||||||
|
return txid, locked_n
|
|
@ -1148,7 +1148,8 @@ class BTCInterface(Secp256k1Interface):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx = self.rpc_wallet('gettransaction', [txid.hex()])
|
# set `include_watchonly` explicitly to `True` to get transactions for watchonly addresses also in BCH
|
||||||
|
tx = self.rpc_wallet('gettransaction', [txid.hex(), True])
|
||||||
|
|
||||||
block_height = 0
|
block_height = 0
|
||||||
if 'blockhash' in tx:
|
if 'blockhash' in tx:
|
||||||
|
|
|
@ -189,7 +189,7 @@ class Address:
|
||||||
:rtype: ``str``
|
:rtype: ``str``
|
||||||
"""
|
"""
|
||||||
version_bit = Address.VERSIONS[self.version]["version_bit"]
|
version_bit = Address.VERSIONS[self.version]["version_bit"]
|
||||||
payload = [version_bit] + self.payload
|
payload = [version_bit] + list(self.payload)
|
||||||
payload = convertbits(payload, 8, 5)
|
payload = convertbits(payload, 8, 5)
|
||||||
checksum = calculate_checksum(self.prefix, payload)
|
checksum = calculate_checksum(self.prefix, payload)
|
||||||
return self.prefix + ":" + b32encode(payload + checksum)
|
return self.prefix + ":" + b32encode(payload + checksum)
|
||||||
|
|
|
@ -191,7 +191,11 @@ def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
|
||||||
class XmrSwapInterface(ProtocolInterface):
|
class XmrSwapInterface(ProtocolInterface):
|
||||||
swap_type = SwapTypes.XMR_SWAP
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
|
|
||||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes) -> CScript:
|
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
||||||
|
# fallthrough to ci if genScriptLockTxScript is implemented there
|
||||||
|
if hasattr(ci, 'genScriptLockTxScript') and callable(ci.genScriptLockTxScript):
|
||||||
|
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
|
||||||
|
|
||||||
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
|
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
|
||||||
Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
|
Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
|
||||||
|
|
||||||
|
@ -221,82 +225,3 @@ class XmrSwapInterface(ProtocolInterface):
|
||||||
ctx.nLockTime = 0
|
ctx.nLockTime = 0
|
||||||
|
|
||||||
return ctx.serialize()
|
return ctx.serialize()
|
||||||
|
|
||||||
class XmrBchSwapInterface(ProtocolInterface):
|
|
||||||
swap_type = SwapTypes.XMR_BCH_SWAP
|
|
||||||
|
|
||||||
def genScriptLockTxScript(self, mining_fee: int, out_1: bytes, out_2: bytes, public_key: bytes, timelock: int) -> CScript:
|
|
||||||
return CScript([
|
|
||||||
# // v4.1.0-CashTokens-Optimized
|
|
||||||
# // Based on swaplock.cash v4.1.0-CashTokens
|
|
||||||
#
|
|
||||||
# // Alice has XMR, wants BCH and/or CashTokens.
|
|
||||||
# // Bob has BCH and/or CashTokens, wants XMR.
|
|
||||||
#
|
|
||||||
# // Verify 1-in-1-out TX form
|
|
||||||
CScriptOp(OP_TXINPUTCOUNT),
|
|
||||||
CScriptOp(OP_1), CScriptOp(OP_NUMEQUALVERIFY),
|
|
||||||
CScriptOp(OP_TXOUTPUTCOUNT),
|
|
||||||
CScriptOp(OP_1), CScriptOp(OP_NUMEQUALVERIFY),
|
|
||||||
|
|
||||||
# // int miningFee
|
|
||||||
mining_fee,
|
|
||||||
# // Verify pre-agreed mining fee and that the rest of BCH is forwarded
|
|
||||||
# // to the output.
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_UTXOVALUE),
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_OUTPUTVALUE),
|
|
||||||
CScriptOp(OP_SUB), CScriptOp(OP_NUMEQUALVERIFY),
|
|
||||||
|
|
||||||
# # // Verify that any CashTokens are forwarded to the output.
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENCATEGORY),
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENCATEGORY),
|
|
||||||
CScriptOp(OP_EQUALVERIFY),
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENCOMMITMENT),
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENCOMMITMENT),
|
|
||||||
CScriptOp(OP_EQUALVERIFY),
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENAMOUNT),
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENAMOUNT),
|
|
||||||
CScriptOp(OP_NUMEQUALVERIFY),
|
|
||||||
|
|
||||||
# // If sequence is not used then it is a regular swap TX.
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_INPUTSEQUENCENUMBER),
|
|
||||||
CScriptOp(OP_NOTIF),
|
|
||||||
# // bytes aliceOutput
|
|
||||||
out_1,
|
|
||||||
# // Verify that the BCH and/or CashTokens are forwarded to Alice's
|
|
||||||
# // output.
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_OUTPUTBYTECODE),
|
|
||||||
CScriptOp(OP_OVER), CScriptOp(OP_EQUALVERIFY),
|
|
||||||
|
|
||||||
# // pubkey bobPubkeyVES
|
|
||||||
public_key,
|
|
||||||
# // Require Alice to decrypt and publish Bob's VES signature.
|
|
||||||
# // The "message" signed is simply a sha256 hash of Alice's output
|
|
||||||
# // locking bytecode.
|
|
||||||
# // By decrypting Bob's VES and publishing it, Alice reveals her
|
|
||||||
# // XMR key share to Bob.
|
|
||||||
CScriptOp(OP_CHECKDATASIG),
|
|
||||||
|
|
||||||
# // If a TX using this path is mined then Alice gets her BCH.
|
|
||||||
# // Bob uses the revealed XMR key share to collect his XMR.
|
|
||||||
|
|
||||||
# // Refund will become available when timelock expires, and it would
|
|
||||||
# // expire because Alice didn't collect on time, either of her own accord
|
|
||||||
# // or because Bob bailed out and witheld the encrypted signature.
|
|
||||||
CScriptOp(OP_ELSE),
|
|
||||||
# // int timelock_0
|
|
||||||
timelock,
|
|
||||||
# // Verify refund timelock.
|
|
||||||
CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
|
|
||||||
|
|
||||||
# // bytes refundLockingBytecode
|
|
||||||
out_2,
|
|
||||||
|
|
||||||
# // Verify that the BCH and/or CashTokens are forwarded to Refund
|
|
||||||
# // contract.
|
|
||||||
CScriptOp(OP_0), CScriptOp(OP_OUTPUTBYTECODE),
|
|
||||||
CScriptOp(OP_EQUAL),
|
|
||||||
|
|
||||||
# // BCH and/or CashTokens are simply forwarded to Refund contract.
|
|
||||||
CScriptOp(OP_ENDIF)
|
|
||||||
])
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import random
|
||||||
import logging
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from basicswap.chainparams import XMR_COIN
|
||||||
from basicswap.db import (
|
from basicswap.db import (
|
||||||
Concepts,
|
Concepts,
|
||||||
)
|
)
|
||||||
|
@ -23,6 +24,7 @@ from basicswap.basicswap_util import (
|
||||||
EventLogTypes,
|
EventLogTypes,
|
||||||
)
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
|
COIN,
|
||||||
make_int,
|
make_int,
|
||||||
format_amount,
|
format_amount,
|
||||||
)
|
)
|
||||||
|
@ -107,9 +109,7 @@ class TestFunctions(BaseTest):
|
||||||
mining_fee = 1000
|
mining_fee = 1000
|
||||||
timelock = 2
|
timelock = 2
|
||||||
a_receive = ci.getNewAddress()
|
a_receive = ci.getNewAddress()
|
||||||
b_receive = ci.getNewAddress()
|
|
||||||
b_refund = ci.getNewAddress()
|
b_refund = ci.getNewAddress()
|
||||||
print(pi)
|
|
||||||
refund_lock_tx_script = pi.genScriptLockTxScript(mining_fee=mining_fee, out_1=ci.addressToLockingBytecode(b_refund), out_2=ci.addressToLockingBytecode(a_receive), public_key=A, timelock=timelock)
|
refund_lock_tx_script = pi.genScriptLockTxScript(mining_fee=mining_fee, out_1=ci.addressToLockingBytecode(b_refund), out_2=ci.addressToLockingBytecode(a_receive), public_key=A, timelock=timelock)
|
||||||
addr_out = ci.getNewAddress()
|
addr_out = ci.getNewAddress()
|
||||||
|
|
||||||
|
@ -124,11 +124,10 @@ class TestFunctions(BaseTest):
|
||||||
assert (len(unspents) > len(unspents_after))
|
assert (len(unspents) > len(unspents_after))
|
||||||
|
|
||||||
tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()])
|
tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()])
|
||||||
print(tx_decoded)
|
|
||||||
txid = tx_decoded['txid']
|
txid = tx_decoded['txid']
|
||||||
|
|
||||||
size = tx_decoded['size']
|
vsize = tx_decoded['size']
|
||||||
expect_fee_int = round(fee_rate * size)
|
expect_fee_int = round(fee_rate * vsize)
|
||||||
expect_fee = ci.format_amount(expect_fee_int)
|
expect_fee = ci.format_amount(expect_fee_int)
|
||||||
|
|
||||||
out_value: int = 0
|
out_value: int = 0
|
||||||
|
@ -147,7 +146,6 @@ class TestFunctions(BaseTest):
|
||||||
|
|
||||||
ci.rpc('sendrawtransaction', [lock_tx.hex()])
|
ci.rpc('sendrawtransaction', [lock_tx.hex()])
|
||||||
rv = ci.rpc('gettransaction', [txid])
|
rv = ci.rpc('gettransaction', [txid])
|
||||||
print(rv)
|
|
||||||
wallet_tx_fee = -ci.make_int(rv['fee'])
|
wallet_tx_fee = -ci.make_int(rv['fee'])
|
||||||
|
|
||||||
assert (wallet_tx_fee == fee_value)
|
assert (wallet_tx_fee == fee_value)
|
||||||
|
@ -165,28 +163,24 @@ class TestFunctions(BaseTest):
|
||||||
|
|
||||||
# alice decrypts the adaptor signature
|
# alice decrypts the adaptor signature
|
||||||
bAdaptorSig_dec = ecdsaotves_dec_sig(a, bAdaptorSig)
|
bAdaptorSig_dec = ecdsaotves_dec_sig(a, bAdaptorSig)
|
||||||
print("\nbAdaptorSig_dec", bAdaptorSig_dec.hex())
|
|
||||||
|
|
||||||
print(ci.addressToLockingBytecode(a_receive).hex(), msg.hex(), bAdaptorSig_dec.hex(), B.hex())
|
|
||||||
|
|
||||||
fee_info = {}
|
fee_info = {}
|
||||||
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, mining_fee, ves=bAdaptorSig_dec, fee_info=fee_info)
|
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, mining_fee, ves=bAdaptorSig_dec, fee_info=fee_info)
|
||||||
print(lock_spend_tx.hex())
|
vsize_estimated: int = fee_info['vsize']
|
||||||
size_estimated: int = fee_info['size']
|
|
||||||
|
|
||||||
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
|
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
|
||||||
print(tx_decoded)
|
print(tx_decoded)
|
||||||
txid = tx_decoded['txid']
|
txid = tx_decoded['txid']
|
||||||
|
|
||||||
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
|
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
|
||||||
size_actual: int = tx_decoded['size']
|
vsize_actual: int = tx_decoded['size']
|
||||||
|
|
||||||
assert (size_actual <= size_estimated and size_estimated - size_actual < 4)
|
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4)
|
||||||
assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
|
assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
|
||||||
|
|
||||||
expect_size: int = ci.xmr_swap_a_lock_spend_tx_vsize()
|
expect_size: int = ci.xmr_swap_a_lock_spend_tx_vsize()
|
||||||
assert (expect_size >= size_actual)
|
assert (expect_size >= vsize_actual)
|
||||||
assert (expect_size - size_actual < 10)
|
assert (expect_size - vsize_actual < 10)
|
||||||
|
|
||||||
# Test chain b (no-script) lock tx size
|
# Test chain b (no-script) lock tx size
|
||||||
v = ci.getNewSecretKey()
|
v = ci.getNewSecretKey()
|
||||||
|
@ -204,3 +198,27 @@ class TestFunctions(BaseTest):
|
||||||
expect_size: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
expect_size: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
assert (expect_size >= lock_tx_b_spend_decoded['size'])
|
assert (expect_size >= lock_tx_b_spend_decoded['size'])
|
||||||
assert (expect_size - lock_tx_b_spend_decoded['size'] < 10)
|
assert (expect_size - lock_tx_b_spend_decoded['size'] < 10)
|
||||||
|
|
||||||
|
def test_05_bch_xmr(self):
|
||||||
|
logging.info('---------- Test BCH to XMR')
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
offer_id = swap_clients[0].postOffer(Coins.BCH, Coins.XMR, 10 * COIN, 100 * XMR_COIN, 10 * COIN, SwapTypes.XMR_SWAP)
|
||||||
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||||
|
offers = swap_clients[1].listOffers(filters={'offer_id': offer_id})
|
||||||
|
offer = offers[0]
|
||||||
|
|
||||||
|
swap_clients[1].ci(Coins.XMR).setFeePriority(3)
|
||||||
|
|
||||||
|
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[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)
|
||||||
|
|
||||||
|
swap_clients[1].ci(Coins.XMR).setFeePriority(0)
|
||||||
|
|
|
@ -98,11 +98,11 @@ from basicswap.bin.run import startDaemon, startXmrDaemon, startXmrWalletDaemon
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
NUM_NODES = 3
|
NUM_NODES = 2
|
||||||
NUM_XMR_NODES = 3
|
NUM_XMR_NODES = 2
|
||||||
NUM_BTC_NODES = 3
|
NUM_BTC_NODES = 2
|
||||||
NUM_BCH_NODES = 3
|
NUM_BCH_NODES = 2
|
||||||
NUM_LTC_NODES = 3
|
NUM_LTC_NODES = 2
|
||||||
TEST_DIR = cfg.TEST_DATADIRS
|
TEST_DIR = cfg.TEST_DATADIRS
|
||||||
|
|
||||||
XMR_BASE_P2P_PORT = 17792
|
XMR_BASE_P2P_PORT = 17792
|
||||||
|
|
Loading…
Reference in a new issue