mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-30 22:35:53 +00:00
xmr: Add prefunded itx.
This commit is contained in:
parent
c90fa6f2c6
commit
7d43512845
9 changed files with 156 additions and 35 deletions
|
@ -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 a new issue