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:
tecnovert 2025-01-10 17:47:27 +02:00
parent 73ab5e7391
commit 681122bcca
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
5 changed files with 95 additions and 32 deletions

View file

@ -140,7 +140,6 @@ from .basicswap_util import (
canAcceptBidState,
describeEventEntry,
getLastBidState,
getOfferProofOfFundsHash,
getVoutByAddress,
getVoutByScriptPubKey,
inactive_states,
@ -2117,21 +2116,27 @@ class BasicSwap(BaseApp):
msg_buf.fee_rate_to
) # Unused: TODO - Set priority?
ensure_balance: int = int(amount)
if coin_from in self.scriptless_coins:
ci_from.ensureFunds(msg_buf.amount_from)
# 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:
proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
ensure_balance: int = int(amount)
# If a prefunded txn is not used, check that the wallet balance can cover the tx fee.
if "prefunded_itx" not in extra_options:
pi = self.pi(SwapTypes.XMR_SWAP)
_ = 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.
proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
coin_from_t, ensure_balance, proof_of_funds_hash
)
# TODO: For now proof_of_funds is just a client side check, may need to be sent with offers in future however.
# TODO: Send proof of funds with offer
# proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
# proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
# coin_from_t, ensure_balance, proof_of_funds_hash
# )
offer_bytes = msg_buf.to_bytes()
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()

View file

@ -261,9 +261,7 @@ class PARTInterfaceBlind(PARTInterface):
]
params = [inputs, outputs]
rv = self.rpc_wallet("createrawparttransaction", params)
tx_bytes = bytes.fromhex(rv["hex"])
return tx_bytes
return bytes.fromhex(rv["hex"])
def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes:
feerate_str = self.format_amount(feerate)
@ -292,7 +290,7 @@ class PARTInterfaceBlind(PARTInterface):
"lockUnspents": True,
"feeRate": feerate_str,
}
rv = self.rpc(
rv = self.rpc_wallet(
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
)
return bytes.fromhex(rv["hex"])
@ -1162,10 +1160,44 @@ class PARTInterfaceBlind(PARTInterface):
sub_fee: bool = False,
lock_unspents: bool = True,
) -> str:
txn = self.rpc_wallet(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
# Estimate lock tx size / fee
# 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 = {
"lockUnspents": lock_unspents,
"conf_target": self._conf_target,
@ -1174,7 +1206,9 @@ class PARTInterfaceBlind(PARTInterface):
options["subtractFeeFromOutputs"] = [
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):

View file

@ -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)
address = ci.getAddressFromKeys(key_view, key_spend)
expect_address = self.getCachedMainWalletAddress(ci)
expect_address = swap_client.getCachedMainWalletAddress(ci)
rv.update(
{
"key_view": ci.encodeKey(key_view),

View file

@ -1734,6 +1734,22 @@ class BasicSwapTest(TestFunctions):
amt_swap: int = ci_from.make_int(balance_from_before, 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(
coin_from,
coin_to,
@ -1745,26 +1761,32 @@ class BasicSwapTest(TestFunctions):
)
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)
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(
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_RECEIVED,
wait_for=20,
(BidStates.BID_ACCEPTED, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED),
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):
tla_from = self.test_coin_from.name
logging.info("---------- Test {} Insufficient Funds (reverse)".format(tla_from))

View file

@ -1793,7 +1793,9 @@ class Test(BaseTest):
self.prepare_balance(Coins.XMR, 20.0, 1800, 1801)
js_w1_before = read_json_api(1801, "wallets")
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:
offer_id = swap_clients[1].postOffer(
@ -1803,7 +1805,7 @@ class Test(BaseTest):
0,
10 * COIN,
SwapTypes.XMR_SWAP,
extra_options={"amount_to": 10 * XMR_COIN}
extra_options={"amount_to": 10 * XMR_COIN},
)
except Exception as e:
assert "Insufficient funds" in str(e)
@ -1817,7 +1819,7 @@ class Test(BaseTest):
0,
10 * COIN,
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)