mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
Disable duplicate (proof of funds) balance check when sending offer.
Fix for blinded Particl offers. Add fee to reverse offer balance check.
This commit is contained in:
@@ -140,7 +140,6 @@ from .basicswap_util import (
|
|||||||
canAcceptBidState,
|
canAcceptBidState,
|
||||||
describeEventEntry,
|
describeEventEntry,
|
||||||
getLastBidState,
|
getLastBidState,
|
||||||
getOfferProofOfFundsHash,
|
|
||||||
getVoutByAddress,
|
getVoutByAddress,
|
||||||
getVoutByScriptPubKey,
|
getVoutByScriptPubKey,
|
||||||
inactive_states,
|
inactive_states,
|
||||||
@@ -2117,21 +2116,27 @@ class BasicSwap(BaseApp):
|
|||||||
msg_buf.fee_rate_to
|
msg_buf.fee_rate_to
|
||||||
) # Unused: TODO - Set priority?
|
) # Unused: TODO - Set priority?
|
||||||
|
|
||||||
if coin_from in self.scriptless_coins:
|
|
||||||
ci_from.ensureFunds(msg_buf.amount_from)
|
|
||||||
else:
|
|
||||||
proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
|
|
||||||
ensure_balance: int = int(amount)
|
ensure_balance: int = int(amount)
|
||||||
|
if coin_from in self.scriptless_coins:
|
||||||
|
# TODO: Better tx size estimate, xmr_swap_b_lock_tx_vsize could be larger than xmr_swap_b_lock_spend_tx_vsize
|
||||||
|
estimated_fee: int = (
|
||||||
|
msg_buf.fee_rate_from
|
||||||
|
* ci_from.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
|
/ 1000
|
||||||
|
)
|
||||||
|
ci_from.ensureFunds(msg_buf.amount_from + estimated_fee)
|
||||||
|
else:
|
||||||
# If a prefunded txn is not used, check that the wallet balance can cover the tx fee.
|
# If a prefunded txn is not used, check that the wallet balance can cover the tx fee.
|
||||||
if "prefunded_itx" not in extra_options:
|
if "prefunded_itx" not in extra_options:
|
||||||
pi = self.pi(SwapTypes.XMR_SWAP)
|
pi = self.pi(SwapTypes.XMR_SWAP)
|
||||||
_ = pi.getFundedInitiateTxTemplate(ci_from, ensure_balance, False)
|
_ = pi.getFundedInitiateTxTemplate(ci_from, ensure_balance, False)
|
||||||
# TODO: Save the prefunded tx so the fee can't change, complicates multiple offers at the same time.
|
# TODO: Save the prefunded tx so the fee can't change, complicates multiple offers at the same time.
|
||||||
|
|
||||||
proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
|
# TODO: Send proof of funds with offer
|
||||||
coin_from_t, ensure_balance, proof_of_funds_hash
|
# proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
|
||||||
)
|
# proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
|
||||||
# TODO: For now proof_of_funds is just a client side check, may need to be sent with offers in future however.
|
# coin_from_t, ensure_balance, proof_of_funds_hash
|
||||||
|
# )
|
||||||
|
|
||||||
offer_bytes = msg_buf.to_bytes()
|
offer_bytes = msg_buf.to_bytes()
|
||||||
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
|
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
|
||||||
|
|||||||
@@ -261,9 +261,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
]
|
]
|
||||||
params = [inputs, outputs]
|
params = [inputs, outputs]
|
||||||
rv = self.rpc_wallet("createrawparttransaction", params)
|
rv = self.rpc_wallet("createrawparttransaction", params)
|
||||||
|
return bytes.fromhex(rv["hex"])
|
||||||
tx_bytes = bytes.fromhex(rv["hex"])
|
|
||||||
return tx_bytes
|
|
||||||
|
|
||||||
def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes:
|
def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes:
|
||||||
feerate_str = self.format_amount(feerate)
|
feerate_str = self.format_amount(feerate)
|
||||||
@@ -292,7 +290,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
"lockUnspents": True,
|
"lockUnspents": True,
|
||||||
"feeRate": feerate_str,
|
"feeRate": feerate_str,
|
||||||
}
|
}
|
||||||
rv = self.rpc(
|
rv = self.rpc_wallet(
|
||||||
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
|
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
|
||||||
)
|
)
|
||||||
return bytes.fromhex(rv["hex"])
|
return bytes.fromhex(rv["hex"])
|
||||||
@@ -1162,10 +1160,44 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
txn = self.rpc_wallet(
|
# Estimate lock tx size / fee
|
||||||
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
|
||||||
|
# self.createSCLockTx
|
||||||
|
vkbv = self.getNewRandomKey()
|
||||||
|
ephemeral_key = self.getNewRandomKey()
|
||||||
|
ephemeral_pubkey = self.getPubkey(ephemeral_key)
|
||||||
|
assert len(ephemeral_pubkey) == 33
|
||||||
|
nonce = self.getScriptLockTxNonce(vkbv)
|
||||||
|
inputs = []
|
||||||
|
outputs = [
|
||||||
|
{
|
||||||
|
"type": "blind",
|
||||||
|
"amount": self.format_amount(amount),
|
||||||
|
"address": addr_to,
|
||||||
|
"nonce": nonce.hex(),
|
||||||
|
"data": ephemeral_pubkey.hex(),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
params = [inputs, outputs]
|
||||||
|
tx_hex = self.rpc_wallet("createrawparttransaction", params)["hex"]
|
||||||
|
|
||||||
|
# self.fundSCLockTx
|
||||||
|
tx_obj = self.rpc("decoderawtransaction", [tx_hex])
|
||||||
|
|
||||||
|
assert len(tx_obj["vout"]) == 1
|
||||||
|
txo = tx_obj["vout"][0]
|
||||||
|
blinded_info = self.rpc(
|
||||||
|
"rewindrangeproof", [txo["rangeproof"], txo["valueCommitment"], nonce.hex()]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
outputs_info = {
|
||||||
|
0: {
|
||||||
|
"value": blinded_info["amount"],
|
||||||
|
"blind": blinded_info["blind"],
|
||||||
|
"nonce": nonce.hex(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
"lockUnspents": lock_unspents,
|
"lockUnspents": lock_unspents,
|
||||||
"conf_target": self._conf_target,
|
"conf_target": self._conf_target,
|
||||||
@@ -1174,7 +1206,9 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
options["subtractFeeFromOutputs"] = [
|
options["subtractFeeFromOutputs"] = [
|
||||||
0,
|
0,
|
||||||
]
|
]
|
||||||
return self.rpc_wallet("fundrawtransactionfrom", ["blind", txn, options])["hex"]
|
return self.rpc_wallet(
|
||||||
|
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
|
||||||
|
)["hex"]
|
||||||
|
|
||||||
|
|
||||||
class PARTInterfaceAnon(PARTInterface):
|
class PARTInterfaceAnon(PARTInterface):
|
||||||
|
|||||||
@@ -841,7 +841,7 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
|
|||||||
key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True)
|
key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True)
|
||||||
address = ci.getAddressFromKeys(key_view, key_spend)
|
address = ci.getAddressFromKeys(key_view, key_spend)
|
||||||
|
|
||||||
expect_address = self.getCachedMainWalletAddress(ci)
|
expect_address = swap_client.getCachedMainWalletAddress(ci)
|
||||||
rv.update(
|
rv.update(
|
||||||
{
|
{
|
||||||
"key_view": ci.encodeKey(key_view),
|
"key_view": ci.encodeKey(key_view),
|
||||||
|
|||||||
@@ -1734,6 +1734,22 @@ class BasicSwapTest(TestFunctions):
|
|||||||
|
|
||||||
amt_swap: int = ci_from.make_int(balance_from_before, r=1)
|
amt_swap: int = ci_from.make_int(balance_from_before, r=1)
|
||||||
rate_swap: int = ci_to.make_int(2.0, r=1)
|
rate_swap: int = ci_to.make_int(2.0, r=1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
offer_id = swap_clients[id_offerer].postOffer(
|
||||||
|
coin_from,
|
||||||
|
coin_to,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
auto_accept_bids=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Insufficient funds" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
amt_swap -= ci_from.make_int(1)
|
||||||
offer_id = swap_clients[id_offerer].postOffer(
|
offer_id = swap_clients[id_offerer].postOffer(
|
||||||
coin_from,
|
coin_from,
|
||||||
coin_to,
|
coin_to,
|
||||||
@@ -1745,26 +1761,32 @@ class BasicSwapTest(TestFunctions):
|
|||||||
)
|
)
|
||||||
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||||
|
|
||||||
|
# First bid should work
|
||||||
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
|
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
|
||||||
|
|
||||||
event = wait_for_event(
|
|
||||||
test_delay_event,
|
|
||||||
swap_clients[id_offerer],
|
|
||||||
Concepts.BID,
|
|
||||||
bid_id,
|
|
||||||
event_type=EventLogTypes.ERROR,
|
|
||||||
wait_for=60,
|
|
||||||
)
|
|
||||||
assert "Insufficient funds" in event.event_msg
|
|
||||||
|
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
test_delay_event,
|
test_delay_event,
|
||||||
swap_clients[id_offerer],
|
swap_clients[id_offerer],
|
||||||
bid_id,
|
bid_id,
|
||||||
BidStates.BID_RECEIVED,
|
(BidStates.BID_ACCEPTED, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED),
|
||||||
wait_for=20,
|
wait_for=40,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Should be out of funds for second bid (over remaining offer value causes a hard auto accept fail)
|
||||||
|
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_offerer],
|
||||||
|
bid_id,
|
||||||
|
BidStates.BID_AACCEPT_FAIL,
|
||||||
|
wait_for=40,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
swap_clients[id_offerer].acceptBid(bid_id)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Insufficient funds" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
def test_08_insufficient_funds_rev(self):
|
def test_08_insufficient_funds_rev(self):
|
||||||
tla_from = self.test_coin_from.name
|
tla_from = self.test_coin_from.name
|
||||||
logging.info("---------- Test {} Insufficient Funds (reverse)".format(tla_from))
|
logging.info("---------- Test {} Insufficient Funds (reverse)".format(tla_from))
|
||||||
|
|||||||
@@ -1793,7 +1793,9 @@ class Test(BaseTest):
|
|||||||
self.prepare_balance(Coins.XMR, 20.0, 1800, 1801)
|
self.prepare_balance(Coins.XMR, 20.0, 1800, 1801)
|
||||||
js_w1_before = read_json_api(1801, "wallets")
|
js_w1_before = read_json_api(1801, "wallets")
|
||||||
ci1_btc = swap_clients[1].ci(Coins.BTC)
|
ci1_btc = swap_clients[1].ci(Coins.BTC)
|
||||||
btc_total = ci1_btc.make_int(js_w1_before["BTC"]["balance"]) + ci1_btc.make_int(js_w1_before["BTC"]["unconfirmed"])
|
btc_total = ci1_btc.make_int(js_w1_before["BTC"]["balance"]) + ci1_btc.make_int(
|
||||||
|
js_w1_before["BTC"]["unconfirmed"]
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
offer_id = swap_clients[1].postOffer(
|
offer_id = swap_clients[1].postOffer(
|
||||||
@@ -1803,7 +1805,7 @@ class Test(BaseTest):
|
|||||||
0,
|
0,
|
||||||
10 * COIN,
|
10 * COIN,
|
||||||
SwapTypes.XMR_SWAP,
|
SwapTypes.XMR_SWAP,
|
||||||
extra_options={"amount_to": 10 * XMR_COIN}
|
extra_options={"amount_to": 10 * XMR_COIN},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
assert "Insufficient funds" in str(e)
|
assert "Insufficient funds" in str(e)
|
||||||
@@ -1817,7 +1819,7 @@ class Test(BaseTest):
|
|||||||
0,
|
0,
|
||||||
10 * COIN,
|
10 * COIN,
|
||||||
SwapTypes.XMR_SWAP,
|
SwapTypes.XMR_SWAP,
|
||||||
extra_options={"amount_to": 10 * XMR_COIN}
|
extra_options={"amount_to": 10 * XMR_COIN},
|
||||||
)
|
)
|
||||||
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user