xmr: Add prefunded itx.
This commit is contained in:
		
							parent
							
								
									c90fa6f2c6
								
							
						
					
					
						commit
						7d43512845
					
				@ -2424,11 +2424,17 @@ class BasicSwap(BaseApp):
 | 
			
		||||
                xmr_swap.kbsl_dleag = xmr_swap.pkbsl
 | 
			
		||||
 | 
			
		||||
            # MSG2F
 | 
			
		||||
            xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createSCLockTx(
 | 
			
		||||
                bid.amount,
 | 
			
		||||
                xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.vkbv
 | 
			
		||||
            )
 | 
			
		||||
            xmr_swap.a_lock_tx = ci_from.fundSCLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
 | 
			
		||||
            pi = self.pi(SwapTypes.XMR_SWAP)
 | 
			
		||||
            xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript(ci_from, xmr_swap.pkal, xmr_swap.pkaf)
 | 
			
		||||
            prefunded_tx = self.getPreFundedTx(Concepts.OFFER, bid.offer_id, TxTypes.ITX_PRE_FUNDED)
 | 
			
		||||
            if prefunded_tx:
 | 
			
		||||
                xmr_swap.a_lock_tx = pi.promoteMockTx(ci_from, prefunded_tx, xmr_swap.a_lock_tx_script)
 | 
			
		||||
            else:
 | 
			
		||||
                xmr_swap.a_lock_tx = ci_from.createSCLockTx(
 | 
			
		||||
                    bid.amount,
 | 
			
		||||
                    xmr_swap.a_lock_tx_script, xmr_swap.vkbv
 | 
			
		||||
                )
 | 
			
		||||
                xmr_swap.a_lock_tx = ci_from.fundSCLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
 | 
			
		||||
 | 
			
		||||
            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)
 | 
			
		||||
 | 
			
		||||
@ -444,19 +444,11 @@ class BTCInterface(CoinInterface):
 | 
			
		||||
 | 
			
		||||
        return pk1, pk2
 | 
			
		||||
 | 
			
		||||
    def genScriptLockTxScript(self, Kal, Kaf):
 | 
			
		||||
        Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
 | 
			
		||||
        Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf)
 | 
			
		||||
 | 
			
		||||
        return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)])
 | 
			
		||||
 | 
			
		||||
    def createSCLockTx(self, value, Kal, Kaf, vkbv=None):
 | 
			
		||||
        script = self.genScriptLockTxScript(Kal, Kaf)
 | 
			
		||||
    def createSCLockTx(self, value: int, script: bytearray, vkbv=None) -> bytes:
 | 
			
		||||
        tx = CTransaction()
 | 
			
		||||
        tx.nVersion = self.txVersion()
 | 
			
		||||
        tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
 | 
			
		||||
 | 
			
		||||
        return tx.serialize(), script
 | 
			
		||||
        return tx.serialize()
 | 
			
		||||
 | 
			
		||||
    def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
 | 
			
		||||
        return self.fundTx(tx_bytes, feerate)
 | 
			
		||||
@ -1271,6 +1263,7 @@ class BTCInterface(CoinInterface):
 | 
			
		||||
 | 
			
		||||
        sign_for_addr = None
 | 
			
		||||
        for addr, value in unspent_addr.items():
 | 
			
		||||
            print('[rm]', value, amount_for)
 | 
			
		||||
            if value >= amount_for:
 | 
			
		||||
                sign_for_addr = addr
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
@ -117,13 +117,12 @@ class FIROInterface(BTCInterface):
 | 
			
		||||
 | 
			
		||||
        return rv
 | 
			
		||||
 | 
			
		||||
    def createSCLockTx(self, value, Kal, Kaf, vkbv=None):
 | 
			
		||||
        script = self.genScriptLockTxScript(Kal, Kaf)
 | 
			
		||||
    def createSCLockTx(self, value: int, script: bytearray, vkbv=None) -> bytes:
 | 
			
		||||
        tx = CTransaction()
 | 
			
		||||
        tx.nVersion = self.txVersion()
 | 
			
		||||
        tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
 | 
			
		||||
 | 
			
		||||
        return tx.serialize(), script
 | 
			
		||||
        return tx.serialize()
 | 
			
		||||
 | 
			
		||||
    def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
 | 
			
		||||
        return self.fundTx(tx_bytes, feerate)
 | 
			
		||||
 | 
			
		||||
@ -166,8 +166,7 @@ class PARTInterfaceBlind(PARTInterface):
 | 
			
		||||
            ensure(v['result'] is True, 'verifycommitment failed')
 | 
			
		||||
        return output_n, blinded_info
 | 
			
		||||
 | 
			
		||||
    def createSCLockTx(self, value, Kal, Kaf, vkbv):
 | 
			
		||||
        script = self.genScriptLockTxScript(Kal, Kaf)
 | 
			
		||||
    def createSCLockTx(self, value: int, script: bytearray, vkbv) -> bytes:
 | 
			
		||||
 | 
			
		||||
        # Nonce is derived from vkbv, ephemeral_key isn't used
 | 
			
		||||
        ephemeral_key = i2b(self.getNewSecretKey())
 | 
			
		||||
@ -181,7 +180,7 @@ class PARTInterfaceBlind(PARTInterface):
 | 
			
		||||
        rv = self.rpc_callback('createrawparttransaction', params)
 | 
			
		||||
 | 
			
		||||
        tx_bytes = bytes.fromhex(rv['hex'])
 | 
			
		||||
        return tx_bytes, script
 | 
			
		||||
        return tx_bytes
 | 
			
		||||
 | 
			
		||||
    def fundSCLockTx(self, tx_bytes, feerate, vkbv):
 | 
			
		||||
        feerate_str = self.format_amount(feerate)
 | 
			
		||||
 | 
			
		||||
@ -4,9 +4,21 @@
 | 
			
		||||
# Distributed under the MIT software license, see the accompanying
 | 
			
		||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
			
		||||
 | 
			
		||||
from basicswap.script import (
 | 
			
		||||
    OpCodes,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProtocolInterface:
 | 
			
		||||
    swap_type = None
 | 
			
		||||
 | 
			
		||||
    def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
 | 
			
		||||
        raise ValueError('base class')
 | 
			
		||||
 | 
			
		||||
    def getMockScript(self) -> bytearray:
 | 
			
		||||
        return bytearray([
 | 
			
		||||
            OpCodes.OP_RETURN, OpCodes.OP_1])
 | 
			
		||||
 | 
			
		||||
    def getMockScriptScriptPubkey(self, ci) -> bytearray:
 | 
			
		||||
        script = self.getMockScript()
 | 
			
		||||
        return ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
 | 
			
		||||
 | 
			
		||||
@ -76,13 +76,12 @@ def redeemITx(self, bid_id, session):
 | 
			
		||||
class AtomicSwapInterface(ProtocolInterface):
 | 
			
		||||
    swap_type = SwapTypes.SELLER_FIRST
 | 
			
		||||
 | 
			
		||||
    def getMockScript(self) -> bytearray:
 | 
			
		||||
        return bytearray([
 | 
			
		||||
            OpCodes.OP_RETURN, OpCodes.OP_1])
 | 
			
		||||
 | 
			
		||||
    def getMockScriptScriptPubkey(self, ci) -> bytearray:
 | 
			
		||||
    def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
 | 
			
		||||
        script = self.getMockScript()
 | 
			
		||||
        return ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
 | 
			
		||||
        addr_to = ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script)
 | 
			
		||||
        funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
 | 
			
		||||
 | 
			
		||||
        return bytes.fromhex(funded_tx)
 | 
			
		||||
 | 
			
		||||
    def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
 | 
			
		||||
        mock_txo_script = self.getMockScriptScriptPubkey(ci)
 | 
			
		||||
@ -103,11 +102,3 @@ class AtomicSwapInterface(ProtocolInterface):
 | 
			
		||||
 | 
			
		||||
        funded_tx = ctx.serialize()
 | 
			
		||||
        return ci.signTxWithWallet(funded_tx)
 | 
			
		||||
 | 
			
		||||
    def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
 | 
			
		||||
 | 
			
		||||
        script = self.getMockScript()
 | 
			
		||||
        addr_to = ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script)
 | 
			
		||||
        funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
 | 
			
		||||
 | 
			
		||||
        return bytes.fromhex(funded_tx)
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,9 @@ from sqlalchemy.orm import scoped_session
 | 
			
		||||
from basicswap.util import (
 | 
			
		||||
    ensure,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util.script import (
 | 
			
		||||
    getP2WSH,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.chainparams import (
 | 
			
		||||
    Coins,
 | 
			
		||||
)
 | 
			
		||||
@ -18,6 +21,9 @@ from basicswap.basicswap_util import (
 | 
			
		||||
    EventLogTypes,
 | 
			
		||||
)
 | 
			
		||||
from . import ProtocolInterface
 | 
			
		||||
from basicswap.contrib.test_framework.script import (
 | 
			
		||||
    CScript, CScriptOp,
 | 
			
		||||
    OP_CHECKMULTISIG)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def addLockRefundSigs(self, xmr_swap, ci):
 | 
			
		||||
@ -90,3 +96,35 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
 | 
			
		||||
 | 
			
		||||
class XmrSwapInterface(ProtocolInterface):
 | 
			
		||||
    swap_type = SwapTypes.XMR_SWAP
 | 
			
		||||
 | 
			
		||||
    def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes) -> CScript:
 | 
			
		||||
        Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
 | 
			
		||||
        Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
 | 
			
		||||
 | 
			
		||||
        return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)])
 | 
			
		||||
 | 
			
		||||
    def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
 | 
			
		||||
        script = self.getMockScript()
 | 
			
		||||
        addr_to = ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script)
 | 
			
		||||
        funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
 | 
			
		||||
 | 
			
		||||
        return bytes.fromhex(funded_tx)
 | 
			
		||||
 | 
			
		||||
    def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
 | 
			
		||||
        mock_txo_script = self.getMockScriptScriptPubkey(ci)
 | 
			
		||||
        real_txo_script = ci.getScriptDest(script)
 | 
			
		||||
 | 
			
		||||
        found: int = 0
 | 
			
		||||
        ctx = ci.loadTx(mock_tx)
 | 
			
		||||
        for txo in ctx.vout:
 | 
			
		||||
            if txo.scriptPubKey == mock_txo_script:
 | 
			
		||||
                txo.scriptPubKey = real_txo_script
 | 
			
		||||
                found += 1
 | 
			
		||||
 | 
			
		||||
        if found < 1:
 | 
			
		||||
            raise ValueError('Mocked output not found')
 | 
			
		||||
        if found > 1:
 | 
			
		||||
            raise ValueError('Too many mocked outputs found')
 | 
			
		||||
        ctx.nLockTime = 0
 | 
			
		||||
 | 
			
		||||
        return ctx.serialize()
 | 
			
		||||
 | 
			
		||||
@ -560,6 +560,16 @@ class Test(BaseTest):
 | 
			
		||||
        wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
 | 
			
		||||
        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
 | 
			
		||||
 | 
			
		||||
        # Verify expected inputs were used
 | 
			
		||||
        bid, offer = swap_clients[2].getBidAndOffer(bid_id)
 | 
			
		||||
        assert (bid.initiate_tx)
 | 
			
		||||
        wtx = ci.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
 | 
			
		||||
        itx_after = ci.describeTx(wtx['hex'])
 | 
			
		||||
        assert (len(itx_after['vin']) == len(itx_decoded['vin']))
 | 
			
		||||
        for i, txin in enumerate(itx_decoded['vin']):
 | 
			
		||||
            assert (txin['txid'] == itx_after['vin'][i]['txid'])
 | 
			
		||||
            assert (txin['vout'] == itx_after['vin'][i]['vout'])
 | 
			
		||||
 | 
			
		||||
    def pass_99_delay(self):
 | 
			
		||||
        logging.info('Delay')
 | 
			
		||||
        for i in range(60 * 10):
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,7 @@ from tests.basicswap.common import (
 | 
			
		||||
    wait_for_no_offer,
 | 
			
		||||
    wait_for_none_active,
 | 
			
		||||
    wait_for_balance,
 | 
			
		||||
    wait_for_unspent,
 | 
			
		||||
    compare_bid_states,
 | 
			
		||||
    extract_states_from_xu_file,
 | 
			
		||||
    TEST_HTTP_HOST,
 | 
			
		||||
@ -1244,6 +1245,78 @@ class Test(BaseTest):
 | 
			
		||||
        swap_clients[0].abandonBid(bid_id)
 | 
			
		||||
        swap_clients[1].abandonBid(bid_id)
 | 
			
		||||
 | 
			
		||||
    def test_14_sweep_balance(self):
 | 
			
		||||
        logging.info('---------- Test sweep balance offer')
 | 
			
		||||
        swap_clients = self.swap_clients
 | 
			
		||||
 | 
			
		||||
        # Disable staking
 | 
			
		||||
        walletsettings = callnoderpc(2, 'walletsettings', ['stakingoptions', ])
 | 
			
		||||
        walletsettings['enabled'] = False
 | 
			
		||||
        walletsettings = callnoderpc(2, 'walletsettings', ['stakingoptions', walletsettings])
 | 
			
		||||
        walletsettings = callnoderpc(2, 'walletsettings', ['stakingoptions', ])
 | 
			
		||||
        assert (walletsettings['stakingoptions']['enabled'] is False)
 | 
			
		||||
 | 
			
		||||
        # Prepare balance
 | 
			
		||||
        js_w2 = read_json_api(1802, 'wallets')
 | 
			
		||||
        if float(js_w2['PART']['balance']) < 100.0:
 | 
			
		||||
            post_json = {
 | 
			
		||||
                'value': 100,
 | 
			
		||||
                'address': js_w2['PART']['deposit_address'],
 | 
			
		||||
                'subfee': False,
 | 
			
		||||
            }
 | 
			
		||||
            json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/part/withdraw', post_json)
 | 
			
		||||
            assert (len(json_rv['txid']) == 64)
 | 
			
		||||
            wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/part', 'balance', 100.0)
 | 
			
		||||
 | 
			
		||||
        js_w2 = read_json_api(1802, 'wallets')
 | 
			
		||||
        assert (float(js_w2['PART']['balance']) >= 100.0)
 | 
			
		||||
 | 
			
		||||
        js_w2 = read_json_api(1802, 'wallets')
 | 
			
		||||
        post_json = {
 | 
			
		||||
            'value': float(js_w2['PART']['balance']),
 | 
			
		||||
            'address': read_json_api(1802, 'wallets/part/nextdepositaddr'),
 | 
			
		||||
            'subfee': True,
 | 
			
		||||
        }
 | 
			
		||||
        json_rv = read_json_api(TEST_HTTP_PORT + 2, 'wallets/part/withdraw', post_json)
 | 
			
		||||
        wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/part', 'balance', 10.0)
 | 
			
		||||
        assert (len(json_rv['txid']) == 64)
 | 
			
		||||
 | 
			
		||||
        # Create prefunded ITX
 | 
			
		||||
        ci = swap_clients[2].ci(Coins.PART)
 | 
			
		||||
        pi = swap_clients[2].pi(SwapTypes.XMR_SWAP)
 | 
			
		||||
        js_w2 = read_json_api(1802, 'wallets')
 | 
			
		||||
        swap_value = ci.make_int(js_w2['PART']['balance'])
 | 
			
		||||
 | 
			
		||||
        itx = pi.getFundedInitiateTxTemplate(ci, swap_value, True)
 | 
			
		||||
        itx_decoded = ci.describeTx(itx.hex())
 | 
			
		||||
        value_after_subfee = ci.make_int(itx_decoded['vout'][0]['value'])
 | 
			
		||||
        assert (value_after_subfee < swap_value)
 | 
			
		||||
        swap_value = value_after_subfee
 | 
			
		||||
        wait_for_unspent(test_delay_event, ci, swap_value)
 | 
			
		||||
 | 
			
		||||
        extra_options = {'prefunded_itx': itx}
 | 
			
		||||
        offer_id = swap_clients[2].postOffer(Coins.PART, Coins.XMR, swap_value, 2 * COIN, swap_value, SwapTypes.XMR_SWAP, extra_options=extra_options)
 | 
			
		||||
 | 
			
		||||
        wait_for_offer(test_delay_event, swap_clients[1], offer_id)
 | 
			
		||||
        offer = swap_clients[1].getOffer(offer_id)
 | 
			
		||||
        bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
 | 
			
		||||
 | 
			
		||||
        wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED)
 | 
			
		||||
        swap_clients[2].acceptBid(bid_id)
 | 
			
		||||
 | 
			
		||||
        wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
 | 
			
		||||
        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
 | 
			
		||||
 | 
			
		||||
        # Verify expected inputs were used
 | 
			
		||||
        bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
 | 
			
		||||
        assert (bid.xmr_a_lock_tx)
 | 
			
		||||
        wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
 | 
			
		||||
        itx_after = ci.describeTx(wtx['hex'])
 | 
			
		||||
        assert (len(itx_after['vin']) == len(itx_decoded['vin']))
 | 
			
		||||
        for i, txin in enumerate(itx_decoded['vin']):
 | 
			
		||||
            assert (txin['txid'] == itx_after['vin'][i]['txid'])
 | 
			
		||||
            assert (txin['vout'] == itx_after['vin'][i]['vout'])
 | 
			
		||||
 | 
			
		||||
    def test_98_withdraw_all(self):
 | 
			
		||||
        logging.info('---------- Test XMR withdrawal all')
 | 
			
		||||
        try:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user